Autopsy  4.21.0
Graphical digital forensics platform for The Sleuth Kit and other tools.
SQLiteViewer.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2018-2021 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.contentviewers;
20 
21 import java.awt.BorderLayout;
22 import java.awt.Component;
23 import java.awt.Cursor;
24 import java.io.File;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.LinkedHashMap;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35 import java.util.concurrent.ExecutionException;
36 import java.util.function.Consumer;
37 import java.util.logging.Level;
38 import javax.swing.JComboBox;
39 import javax.swing.JFileChooser;
40 import javax.swing.JOptionPane;
41 import javax.swing.SwingWorker;
42 import javax.swing.filechooser.FileNameExtensionFilter;
43 import org.apache.commons.io.FilenameUtils;
44 import org.openide.util.NbBundle;
45 import org.openide.windows.WindowManager;
50 import org.sleuthkit.datamodel.AbstractFile;
53 
57 @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
58 class SQLiteViewer extends javax.swing.JPanel implements FileTypeViewer {
59 
60  private static final long serialVersionUID = 1L;
61  public static final String[] SUPPORTED_MIMETYPES = new String[]{"application/x-sqlite3"};
62  private static final int ROWS_PER_PAGE = 100;
63  private static final Logger logger = Logger.getLogger(FileViewer.class.getName());
64  private final SQLiteTableView selectedTableView = new SQLiteTableView();
65  private AbstractFile sqliteDbFile;
66 
67  private SQLiteTableReader viewReader;
68 
69  private Map<String, Object> row = new LinkedHashMap<>();
70  private List<Map<String, Object>> pageOfTableRows = new ArrayList<>();
71  private List<String> currentTableHeader = new ArrayList<>();
72  private String prevTableName;
73 
74  private int numRows; // num of rows in the selected table
75  private int currPage = 0; // curr page of rows being displayed
76 
77  SwingWorker<?, ?> worker;
78 
79  private final JFileChooserFactory chooserHelper = new JFileChooserFactory();
80 
84  SQLiteViewer() {
85  initComponents();
86  jTableDataPanel.add(selectedTableView, BorderLayout.CENTER);
87  }
88 
94  @SuppressWarnings("unchecked")
95  // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
96  private void initComponents() {
97 
98  jHdrPanel = new javax.swing.JPanel();
99  tablesDropdownList = new javax.swing.JComboBox<>();
100  jLabel1 = new javax.swing.JLabel();
101  numEntriesField = new javax.swing.JTextField();
102  jLabel2 = new javax.swing.JLabel();
103  currPageLabel = new javax.swing.JLabel();
104  jLabel3 = new javax.swing.JLabel();
105  numPagesLabel = new javax.swing.JLabel();
106  prevPageButton = new javax.swing.JButton();
107  nextPageButton = new javax.swing.JButton();
108  exportCsvButton = new javax.swing.JButton();
109  jTableDataPanel = new javax.swing.JPanel();
110 
111  jHdrPanel.setPreferredSize(new java.awt.Dimension(536, 40));
112 
113  tablesDropdownList.setModel(new javax.swing.DefaultComboBoxModel<>(new String[] { "Item 1", "Item 2", "Item 3", "Item 4" }));
114  tablesDropdownList.addActionListener(new java.awt.event.ActionListener() {
115  public void actionPerformed(java.awt.event.ActionEvent evt) {
116  tablesDropdownListActionPerformed(evt);
117  }
118  });
119 
120  org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel1.text")); // NOI18N
121 
122  numEntriesField.setEditable(false);
123  numEntriesField.setText(org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numEntriesField.text")); // NOI18N
124  numEntriesField.setBorder(null);
125 
126  org.openide.awt.Mnemonics.setLocalizedText(jLabel2, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel2.text")); // NOI18N
127 
128  org.openide.awt.Mnemonics.setLocalizedText(currPageLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.currPageLabel.text")); // NOI18N
129 
130  org.openide.awt.Mnemonics.setLocalizedText(jLabel3, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.jLabel3.text")); // NOI18N
131 
132  org.openide.awt.Mnemonics.setLocalizedText(numPagesLabel, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.numPagesLabel.text")); // NOI18N
133 
134  prevPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back.png"))); // NOI18N
135  org.openide.awt.Mnemonics.setLocalizedText(prevPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.prevPageButton.text")); // NOI18N
136  prevPageButton.setBorderPainted(false);
137  prevPageButton.setContentAreaFilled(false);
138  prevPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_back_disabled.png"))); // NOI18N
139  prevPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
140  prevPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
141  prevPageButton.addActionListener(new java.awt.event.ActionListener() {
142  public void actionPerformed(java.awt.event.ActionEvent evt) {
143  prevPageButtonActionPerformed(evt);
144  }
145  });
146 
147  nextPageButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward.png"))); // NOI18N
148  org.openide.awt.Mnemonics.setLocalizedText(nextPageButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.nextPageButton.text")); // NOI18N
149  nextPageButton.setBorderPainted(false);
150  nextPageButton.setContentAreaFilled(false);
151  nextPageButton.setDisabledSelectedIcon(new javax.swing.ImageIcon(getClass().getResource("/org/sleuthkit/autopsy/corecomponents/btn_step_forward_disabled.png"))); // NOI18N
152  nextPageButton.setMargin(new java.awt.Insets(2, 0, 2, 0));
153  nextPageButton.setPreferredSize(new java.awt.Dimension(23, 23));
154  nextPageButton.addActionListener(new java.awt.event.ActionListener() {
155  public void actionPerformed(java.awt.event.ActionEvent evt) {
156  nextPageButtonActionPerformed(evt);
157  }
158  });
159 
160  org.openide.awt.Mnemonics.setLocalizedText(exportCsvButton, org.openide.util.NbBundle.getMessage(SQLiteViewer.class, "SQLiteViewer.exportCsvButton.text")); // NOI18N
161  exportCsvButton.addActionListener(new java.awt.event.ActionListener() {
162  public void actionPerformed(java.awt.event.ActionEvent evt) {
163  exportCsvButtonActionPerformed(evt);
164  }
165  });
166 
167  javax.swing.GroupLayout jHdrPanelLayout = new javax.swing.GroupLayout(jHdrPanel);
168  jHdrPanel.setLayout(jHdrPanelLayout);
169  jHdrPanelLayout.setHorizontalGroup(
170  jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
171  .addGroup(jHdrPanelLayout.createSequentialGroup()
172  .addContainerGap()
173  .addComponent(jLabel1)
174  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
175  .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, 130, javax.swing.GroupLayout.PREFERRED_SIZE)
176  .addGap(18, 18, 18)
177  .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, 71, javax.swing.GroupLayout.PREFERRED_SIZE)
178  .addGap(15, 15, 15)
179  .addComponent(jLabel2)
180  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
181  .addComponent(currPageLabel)
182  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
183  .addComponent(jLabel3)
184  .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
185  .addComponent(numPagesLabel)
186  .addGap(18, 18, 18)
187  .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
188  .addGap(0, 0, 0)
189  .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
190  .addGap(29, 29, 29)
191  .addComponent(exportCsvButton)
192  .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
193  );
194  jHdrPanelLayout.setVerticalGroup(
195  jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
196  .addGroup(jHdrPanelLayout.createSequentialGroup()
197  .addContainerGap()
198  .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
199  .addComponent(exportCsvButton)
200  .addComponent(nextPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
201  .addComponent(prevPageButton, javax.swing.GroupLayout.PREFERRED_SIZE, 23, javax.swing.GroupLayout.PREFERRED_SIZE)
202  .addGroup(jHdrPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
203  .addComponent(tablesDropdownList, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
204  .addComponent(jLabel1)
205  .addComponent(numEntriesField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
206  .addComponent(jLabel2)
207  .addComponent(currPageLabel)
208  .addComponent(jLabel3)
209  .addComponent(numPagesLabel)))
210  .addContainerGap())
211  );
212 
213  jTableDataPanel.setLayout(new java.awt.BorderLayout());
214 
215  javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
216  this.setLayout(layout);
217  layout.setHorizontalGroup(
218  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
219  .addComponent(jHdrPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 569, Short.MAX_VALUE)
220  .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
221  );
222  layout.setVerticalGroup(
223  layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
224  .addGroup(layout.createSequentialGroup()
225  .addComponent(jHdrPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 53, javax.swing.GroupLayout.PREFERRED_SIZE)
226  .addGap(0, 0, 0)
227  .addComponent(jTableDataPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 317, Short.MAX_VALUE))
228  );
229  }// </editor-fold>//GEN-END:initComponents
230 
231  private void nextPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_nextPageButtonActionPerformed
232  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
233  currPage++;
234  if (currPage * ROWS_PER_PAGE > numRows) {
235  nextPageButton.setEnabled(false);
236  }
237  currPageLabel.setText(Integer.toString(currPage));
238  prevPageButton.setEnabled(true);
239 
240  // read and display a page of rows
241  String tableName = (String) this.tablesDropdownList.getSelectedItem();
242  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
243  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
244  }//GEN-LAST:event_nextPageButtonActionPerformed
245 
246  private void prevPageButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_prevPageButtonActionPerformed
247 
248  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
249  currPage--;
250  if (currPage == 1) {
251  prevPageButton.setEnabled(false);
252  }
253  currPageLabel.setText(Integer.toString(currPage));
254  nextPageButton.setEnabled(true);
255 
256  // read and display a page of rows
257  String tableName = (String) this.tablesDropdownList.getSelectedItem();
258  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
259  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
260  }//GEN-LAST:event_prevPageButtonActionPerformed
261 
262  private void tablesDropdownListActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_tablesDropdownListActionPerformed
263  JComboBox<?> cb = (JComboBox<?>) evt.getSource();
264  String tableName = (String) cb.getSelectedItem();
265  if (null == tableName) {
266  return;
267  }
268  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
269  selectTable(tableName);
270  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
271  }//GEN-LAST:event_tablesDropdownListActionPerformed
272 
280  @NbBundle.Messages({"SQLiteViewer.csvExport.fileName.empty=Please input a file name for exporting.",
281  "SQLiteViewer.csvExport.title=Export to csv file",
282  "SQLiteViewer.csvExport.confirm.msg=Do you want to overwrite the existing file?"})
283  private void exportCsvButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_exportCsvButtonActionPerformed
284  Case openCase = Case.getCurrentCase();
285  File caseDirectory = new File(openCase.getExportDirectory());
286  JFileChooser fileChooser = chooserHelper.getChooser();
287  fileChooser.setDragEnabled(false);
288  fileChooser.setCurrentDirectory(caseDirectory);
289  //Set a filter to let the filechooser only work for csv files
290  FileNameExtensionFilter csvFilter = new FileNameExtensionFilter("*.csv", "csv");
291  fileChooser.addChoosableFileFilter(csvFilter);
292  fileChooser.setAcceptAllFileFilterUsed(true);
293  fileChooser.setFileFilter(csvFilter);
294  fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
295  String defaultFileName = (String) this.tablesDropdownList.getSelectedItem();
296  fileChooser.setSelectedFile(new File(defaultFileName));
297  int choice = fileChooser.showSaveDialog((Component) evt.getSource()); //TODO
298  if (JFileChooser.APPROVE_OPTION == choice) {
299  File file = fileChooser.getSelectedFile();
300  if (file.exists() && FilenameUtils.getExtension(file.getName()).equalsIgnoreCase("csv")) {
301  if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this,
302  Bundle.SQLiteViewer_csvExport_confirm_msg(),
303  Bundle.SQLiteViewer_csvExport_title(),
304  JOptionPane.YES_NO_OPTION)) {
305  } else {
306  return;
307  }
308  }
309 
310  exportTableToCsv(file);
311  }
312  }//GEN-LAST:event_exportCsvButtonActionPerformed
313 
314  // Variables declaration - do not modify//GEN-BEGIN:variables
315  private javax.swing.JLabel currPageLabel;
316  private javax.swing.JButton exportCsvButton;
317  private javax.swing.JPanel jHdrPanel;
318  private javax.swing.JLabel jLabel1;
319  private javax.swing.JLabel jLabel2;
320  private javax.swing.JLabel jLabel3;
321  private javax.swing.JPanel jTableDataPanel;
322  private javax.swing.JButton nextPageButton;
323  private javax.swing.JTextField numEntriesField;
324  private javax.swing.JLabel numPagesLabel;
325  private javax.swing.JButton prevPageButton;
326  private javax.swing.JComboBox<String> tablesDropdownList;
327  // End of variables declaration//GEN-END:variables
328 
329  @Override
330  public List<String> getSupportedMIMETypes() {
331  return Arrays.asList(SUPPORTED_MIMETYPES);
332  }
333 
334  @Override
335  public void setFile(AbstractFile file) {
336  if (worker != null) {
337  worker.cancel(true);
338  worker = null;
339  }
340  resetComponent();
341 
342  if (file == null) {
343  return;
344  }
345 
346  processSQLiteFile(file);
347  }
348 
349  @Override
350  public Component getComponent() {
351  return this;
352  }
353 
354  @Override
355  public void resetComponent() {
356  tablesDropdownList.setEnabled(true);
357  tablesDropdownList.removeAllItems();
358  numEntriesField.setText("");
359 
360  if(viewReader != null) {
361  try {
362  viewReader.close();
363  } catch (SQLiteTableReaderException ex) {
364  //Could not successfully close the reader, nothing we can do to recover.
365  }
366  }
367  row = new LinkedHashMap<>();
368  pageOfTableRows = new ArrayList<>();
369  currentTableHeader = new ArrayList<>();
370  viewReader = null;
371  sqliteDbFile = null;
372  }
373 
377  @NbBundle.Messages({
378  "SQLiteViewer.comboBox.noTableEntry=No tables found",
379  "SQLiteViewer.errorMessage.interrupted=The processing of the file was interrupted.",
380  "SQLiteViewer.errorMessage.noCurrentCase=The case has been closed.",
381  "SQLiteViewer.errorMessage.failedToExtractFile=The file could not be extracted from the data source.",
382  "SQLiteViewer.errorMessage.failedToQueryDatabase=The database tables in the file could not be read.",
383  "SQLiteViewer.errorMessage.failedToinitJDBCDriver=The JDBC driver for SQLite could not be loaded.",
384  "# {0} - exception message", "SQLiteViewer.errorMessage.unexpectedError=An unexpected error occurred:\n{0).",})
385  private void processSQLiteFile(final AbstractFile file) {
386 
387  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
388  worker = new SQLiteViewerWorker(file) {
389  @Override
390  public void done() {
391  if (isCancelled()) {
392  return;
393  }
394 
395  WorkerResults results;
396  try {
397  results = get();
398  sqliteDbFile = file;
399  viewReader = results.getReader();
400  tablesDropdownList.removeAllItems();
401  Collection<String> dbTablesMap = results.getDbTablesMap();
402  if (dbTablesMap.isEmpty()) {
403  tablesDropdownList.addItem(Bundle.SQLiteViewer_comboBox_noTableEntry());
404  tablesDropdownList.setEnabled(false);
405  } else {
406  dbTablesMap.forEach((tableName) -> {
407  tablesDropdownList.addItem(tableName);
408  });
409  }
410 
411  WindowManager.getDefault().getMainWindow().setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
412  } catch (InterruptedException | ExecutionException ex) {
413  logger.log(Level.SEVERE, String.format("Failed to display SQL Viewer for file (%d)", file.getId()), ex);
414  }
415  }
416  };
417 
418  worker.execute();
419  }
420 
421  @NbBundle.Messages({"# {0} - tableName",
422  "SQLiteViewer.selectTable.errorText=Error getting row count for table: {0}"
423  })
424  private void selectTable(String tableName) {
425  try {
426  numRows = viewReader.getRowCount(tableName);
427  numEntriesField.setText(numRows + " entries");
428 
429  currPage = 1;
430  currPageLabel.setText(Integer.toString(currPage));
431  numPagesLabel.setText(Integer.toString((numRows / ROWS_PER_PAGE) + 1));
432 
433  prevPageButton.setEnabled(false);
434 
435  if (numRows > 0) {
436  exportCsvButton.setEnabled(true);
437  nextPageButton.setEnabled(((numRows > ROWS_PER_PAGE)));
438  readTable(tableName, (currPage - 1) * ROWS_PER_PAGE + 1, ROWS_PER_PAGE);
439  } else {
440  exportCsvButton.setEnabled(false);
441  nextPageButton.setEnabled(false);
442 
443  currentTableHeader = new ArrayList<>();
444  viewReader.read(tableName);
445  Map<String, Object> columnRow = new LinkedHashMap<>();
446  for (int i = 0; i < currentTableHeader.size(); i++) {
447  columnRow.put(currentTableHeader.get(i), "");
448  }
449  selectedTableView.setupTable(Collections.singletonList(columnRow));
450  }
451  } catch (SQLiteTableReaderException ex) {
452  logger.log(Level.WARNING, String.format("Failed to load table %s " //NON-NLS
453  + "from DB file '%s' (objId=%d)", tableName, sqliteDbFile.getName(), //NON-NLS
454  sqliteDbFile.getId()), ex.getMessage());
455  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_selectTable_errorText(tableName));
456  }
457  }
458 
459  @NbBundle.Messages({"# {0} - tableName",
460  "SQLiteViewer.readTable.errorText=Error getting rows for table: {0}"})
461  private void readTable(String tableName, int startRow, int numRowsToRead) {
462  try {
463  //If the table name has changed, then clear our table header. SQLiteTableReader
464  //will also detect the table name has changed and begin reading it as if it
465  //were a brand new table.
466  if (!tableName.equals(prevTableName)) {
467  prevTableName = tableName;
468  }
469  currentTableHeader = new ArrayList<>();
470  viewReader.read(tableName, numRowsToRead, startRow - 1);
471  selectedTableView.setupTable(pageOfTableRows);
472  pageOfTableRows = new ArrayList<>();
473  } catch (SQLiteTableReaderException ex) {
474  logger.log(Level.WARNING, String.format("Failed to read table %s from DB file '%s' " //NON-NLS
475  + "(objId=%d) starting at row [%d] and limit [%d]", //NON-NLS
476  tableName, sqliteDbFile.getName(), sqliteDbFile.getId(),
477  startRow - 1, numRowsToRead), ex.getMessage());
478  MessageNotifyUtil.Message.error(Bundle.SQLiteViewer_readTable_errorText(tableName));
479  }
480  }
481 
488  private SQLiteTableReader initReader(AbstractFile sqliteFile) {
489  return new SQLiteTableReader.Builder(sqliteFile)
490  .forAllColumnNames((columnName) -> {
491  currentTableHeader.add(columnName);
492  })
493  .forAllTableValues(getForAllStrategy()).build();
494  }
495 
508  private Consumer<Object> getForAllStrategy() {
509  return new Consumer<Object>() {
510  private int rowIndex = 0;
511 
512  @Override
513  public void accept(Object t) {
514  rowIndex++;
515  String objectStr = (t instanceof byte[]) ? "BLOB Data not shown"
516  : Objects.toString(t, "");
517 
518  row.put(currentTableHeader.get(rowIndex - 1), objectStr);
519 
520  //If we have built up a full database row, then add it to our page
521  //of rows to be displayed in the UI.
522  if (rowIndex == currentTableHeader.size()) {
523  pageOfTableRows.add(row);
524  row = new LinkedHashMap<>();
525  }
526  rowIndex %= currentTableHeader.size();
527  }
528 
529  };
530  }
531 
532  private int totalColumnCount;
533 
534  @NbBundle.Messages({"SQLiteViewer.exportTableToCsv.write.errText=Failed to export table content to csv file.",
535  "SQLiteViewer.exportTableToCsv.FileName=File name: ",
536  "SQLiteViewer.exportTableToCsv.TableName=Table name: "
537  })
538  private void exportTableToCsv(File file) {
539  final File csvFile = new File(file.toString() + ".csv");
540  final String tableName = (String) this.tablesDropdownList.getSelectedItem();
541 
542  SwingWorker<String, Void> csvWorker = new SwingWorker<String, Void>() {
543  @Override
544  protected String doInBackground() throws Exception {
545  try (FileOutputStream out = new FileOutputStream(csvFile, false)) {
546  try (SQLiteTableReader sqliteStream = new SQLiteTableReader.Builder(sqliteDbFile)
547  .forAllColumnNames(getColumnNameCSVStrategy(out))
548  .forAllTableValues(getForAllCSVStrategy(out)).build()) {
549  totalColumnCount = sqliteStream.getColumnCount(tableName);
550  sqliteStream.read(tableName);
551  }
552  } catch (IOException | SQLiteTableReaderException | RuntimeException ex) {
553  logger.log(Level.WARNING, String.format("Failed to export table [%s]"
554  + " to CSV in sqlite file '%s' (objId=%d)", tableName, sqliteDbFile.getName(),
555  sqliteDbFile.getId()), ex.getMessage()); //NON-NLS
556 
557  return Bundle.SQLiteViewer_exportTableToCsv_write_errText();
558  }
559  return "";
560  }
561 
562  @Override
563  public void done() {
564  try {
565  String message = get();
566  if (!message.isEmpty()) {
567  MessageNotifyUtil.Message.error(message);
568  }
569  } catch (InterruptedException | ExecutionException ex) {
570  logger.log(Level.SEVERE, "Failure occurred writing sql csv file.", ex);
571  }
572  }
573 
574  };
575 
576  csvWorker.execute();
577  }
578 
593  private Consumer<String> getColumnNameCSVStrategy(FileOutputStream out) {
594  return new Consumer<String>() {
595  private int columnIndex = 0;
596 
597  @Override
598  public void accept(String columnName) {
599  columnIndex++;
600  String csvString = columnName;
601  //Format the value to adhere to the format of a CSV file
602  if (columnIndex == 1) {
603  csvString = "\"" + csvString + "\"";
604  } else {
605  csvString = ",\"" + csvString + "\"";
606  }
607  if (columnIndex == totalColumnCount) {
608  csvString += "\n";
609  }
610 
611  try {
612  out.write(csvString.getBytes());
613  } catch (IOException ex) {
614  /*
615  * If we can no longer write to the output stream, toss a
616  * runtime exception to get out of iteration. We explicitly
617  * catch this in exportTableToCsv() above.
618  */
619  throw new RuntimeException(ex);
620  }
621  }
622  };
623  }
624 
639  private Consumer<Object> getForAllCSVStrategy(FileOutputStream out) {
640  return new Consumer<Object>() {
641  private int rowIndex = 0;
642 
643  @Override
644  public void accept(Object tableValue) {
645  rowIndex++;
646  //Substitute string representation of blob with placeholder text.
647  //Automatically wrap the value in quotes in case it contains commas.
648  String objectStr = (tableValue instanceof byte[])
649  ? "BLOB Data not shown" : Objects.toString(tableValue, "");
650  objectStr = "\"" + objectStr + "\"";
651 
652  if (rowIndex > 1) {
653  objectStr = "," + objectStr;
654  }
655  if (rowIndex == totalColumnCount) {
656  objectStr += "\n";
657  }
658 
659  try {
660  out.write(objectStr.getBytes());
661  } catch (IOException ex) {
662  /*
663  * If we can no longer write to the output stream, toss a
664  * runtime exception to get out of iteration. We explicitly
665  * catch this in exportTableToCsv() above.
666  */
667  throw new RuntimeException(ex);
668  }
669  rowIndex %= totalColumnCount;
670  }
671  };
672  }
673 
674  @Override
675  public boolean isSupported(AbstractFile file) {
676  return true;
677  }
678 
683  private class SQLiteViewerWorker extends SwingWorker<WorkerResults, Void> {
684 
685  private final AbstractFile file;
686 
687  SQLiteViewerWorker(AbstractFile file) {
688  this.file = file;
689  }
690 
691  @Override
692  protected WorkerResults doInBackground() throws Exception {
693  SQLiteTableReader reader = initReader(file);
694  Collection<String> dbTablesMap = reader.getTableNames();
695 
696  return new WorkerResults(reader, dbTablesMap);
697  }
698 
699  }
700 
701  /*
702  * Stores the data gather from the
703  */
704  private class WorkerResults {
705 
706  private final SQLiteTableReader reader;
707  private final Collection<String> dbTablesMap;
708 
709  WorkerResults(SQLiteTableReader reader, Collection<String> dbTablesMap) {
710  this.reader = reader;
711  this.dbTablesMap = dbTablesMap;
712  }
713 
714  SQLiteTableReader getReader() {
715  return reader;
716  }
717 
718  Collection<String> getDbTablesMap() {
719  return dbTablesMap;
720  }
721  }
722 
723 }

Copyright © 2012-2022 Basis Technology. Generated on: Tue Feb 6 2024
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.