mirror of
https://github.com/arsenetar/dupeguru.git
synced 2024-12-21 10:59:03 +00:00
[#189 state:fixed] Added "Export to CSV" feature.
This commit is contained in:
parent
deb5260c6a
commit
fcdc692b61
@ -291,4 +291,18 @@ http://www.hardcoded.net/licenses/bsd_license
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)selectDestFileWithPrompt:(NSString *)prompt extension:(NSString *)extension
|
||||
{
|
||||
NSSavePanel *sp = [NSSavePanel savePanel];
|
||||
[sp setCanCreateDirectories:YES];
|
||||
[sp setAllowedFileTypes:[NSArray arrayWithObject:extension]];
|
||||
[sp setTitle:prompt];
|
||||
if ([sp runModal] == NSOKButton) {
|
||||
return [sp filename];
|
||||
}
|
||||
else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
@ -57,7 +57,6 @@ http://www.hardcoded.net/licenses/bsd_license
|
||||
- (void)changeOptions;
|
||||
- (void)copyMarked;
|
||||
- (void)trashMarked;
|
||||
- (void)exportToXHTML;
|
||||
- (void)filter;
|
||||
- (void)focusOnFilterField;
|
||||
- (void)ignoreSelected;
|
||||
|
@ -151,12 +151,6 @@ http://www.hardcoded.net/licenses/bsd_license
|
||||
[model deleteMarked];
|
||||
}
|
||||
|
||||
- (void)exportToXHTML
|
||||
{
|
||||
NSString *exported = [model exportToXHTML];
|
||||
[[NSWorkspace sharedWorkspace] openFile:exported];
|
||||
}
|
||||
|
||||
- (void)filter
|
||||
{
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
|
@ -28,7 +28,8 @@ appMenu.addItem("Quit dupeGuru", Action(NSApp, 'terminate:'), 'cmd+q')
|
||||
fileMenu.addItem("Load Results...", Action(None, 'loadResults'), 'cmd+o')
|
||||
owner.recentResultsMenu = fileMenu.addMenu("Load Recent Results")
|
||||
fileMenu.addItem("Save Results...", Action(None, 'saveResults'), 'cmd+s')
|
||||
fileMenu.addItem("Export Results to XHTML", Action(None, 'exportToXHTML'), 'cmd+shift+e')
|
||||
fileMenu.addItem("Export Results to XHTML", Action(owner.model, 'exportToXHTML'), 'cmd+shift+e')
|
||||
fileMenu.addItem("Export Results to CSV", Action(owner.model, 'exportToCSV'))
|
||||
if edition == 'pe':
|
||||
fileMenu.addItem("Clear Picture Cache", Action(owner, 'clearPictureCache'), 'cmd+shift+p')
|
||||
elif edition == 'me':
|
||||
|
@ -23,6 +23,7 @@ class DupeGuruView(FairwareView):
|
||||
def askYesNoWithPrompt_(self, prompt: str) -> bool: pass
|
||||
def showProblemDialog(self): pass
|
||||
def selectDestFolderWithPrompt_(self, prompt: str) -> str: pass
|
||||
def selectDestFileWithPrompt_extension_(self, prompt: str, extension: str) -> str: pass
|
||||
|
||||
class PyDupeGuruBase(PyFairware):
|
||||
FOLLOW_PROTOCOLS = ['Worker']
|
||||
@ -65,8 +66,11 @@ class PyDupeGuruBase(PyFairware):
|
||||
def doScan(self):
|
||||
self.model.start_scanning()
|
||||
|
||||
def exportToXHTML(self) -> str:
|
||||
return self.model.export_to_xhtml()
|
||||
def exportToXHTML(self):
|
||||
self.model.export_to_xhtml()
|
||||
|
||||
def exportToCSV(self):
|
||||
self.model.export_to_csv()
|
||||
|
||||
def loadSession(self):
|
||||
self.model.load()
|
||||
@ -217,3 +221,7 @@ class PyDupeGuruBase(PyFairware):
|
||||
def select_dest_folder(self, prompt):
|
||||
return self.callback.selectDestFolderWithPrompt_(prompt)
|
||||
|
||||
@dontwrap
|
||||
def select_dest_file(self, prompt, extension):
|
||||
return self.callback.selectDestFileWithPrompt_extension_(prompt, extension)
|
||||
|
||||
|
34
core/app.py
34
core/app.py
@ -90,6 +90,7 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
# show_results_window()
|
||||
# show_problem_dialog()
|
||||
# select_dest_folder(prompt: str) --> str
|
||||
# select_dest_file(prompt: str, ext: str) --> str
|
||||
|
||||
# in fairware prompts, we don't mention the edition, it's too long.
|
||||
PROMPT_NAME = "dupeGuru"
|
||||
@ -199,6 +200,19 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
except EnvironmentError:
|
||||
return None
|
||||
|
||||
def _get_export_data(self):
|
||||
columns = [col for col in self.result_table.columns.ordered_columns
|
||||
if col.visible and col.name != 'marked']
|
||||
colnames = [col.display for col in columns]
|
||||
rows = []
|
||||
for group_id, group in enumerate(self.results.groups):
|
||||
for dupe in group:
|
||||
data = self.get_display_info(dupe, group)
|
||||
row = [data[col.name] for col in columns]
|
||||
row.insert(0, group_id)
|
||||
rows.append(row)
|
||||
return colnames, rows
|
||||
|
||||
def _results_changed(self):
|
||||
self.selected_dupes = [d for d in self.selected_dupes
|
||||
if self.results.get_group_of_duplicate(d) is not None]
|
||||
@ -356,17 +370,15 @@ class DupeGuru(RegistrableApplication, Broadcaster):
|
||||
self.view.start_job(JobType.Delete, self._do_delete, args=args)
|
||||
|
||||
def export_to_xhtml(self):
|
||||
columns = [col for col in self.result_table.columns.ordered_columns
|
||||
if col.visible and col.name != 'marked']
|
||||
colnames = [col.display for col in columns]
|
||||
rows = []
|
||||
for group in self.results.groups:
|
||||
for dupe in group:
|
||||
data = self.get_display_info(dupe, group)
|
||||
row = [data[col.name] for col in columns]
|
||||
row.insert(0, dupe is not group.ref)
|
||||
rows.append(row)
|
||||
return export.export_to_xhtml(colnames, rows)
|
||||
colnames, rows = self._get_export_data()
|
||||
export_path = export.export_to_xhtml(colnames, rows)
|
||||
self.view.open_path(export_path)
|
||||
|
||||
def export_to_csv(self):
|
||||
dest_file = self.view.select_dest_file(tr("Select a destination for your exported CSV"), 'csv')
|
||||
if dest_file:
|
||||
colnames, rows = self._get_export_data()
|
||||
export.export_to_csv(dest_file, colnames, rows)
|
||||
|
||||
def get_display_info(self, dupe, group, delta=False):
|
||||
def empty_data():
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
import os.path as op
|
||||
from tempfile import mkdtemp
|
||||
import csv
|
||||
|
||||
# Yes, this is a very low-tech solution, but at least it doesn't have all these annoying dependency
|
||||
# and resource problems.
|
||||
@ -119,12 +120,18 @@ def export_to_xhtml(colnames, rows):
|
||||
assert len(rows[0]) == len(colnames) + 1 # + 1 is for the "indented" flag
|
||||
colheaders = ''.join(COLHEADERS_TEMPLATE.format(name=name) for name in colnames)
|
||||
rendered_rows = []
|
||||
previous_group_id = None
|
||||
for row in rows:
|
||||
# [2:] is to remove the indented flag + filename
|
||||
indented = 'indented' if row[0] else ''
|
||||
if row[0] != previous_group_id:
|
||||
# We've just changed dupe group, which means that this dupe is a ref. We don't indent it.
|
||||
indented = ''
|
||||
else:
|
||||
indented = 'indented'
|
||||
filename = row[1]
|
||||
cells = ''.join(CELL_TEMPLATE.format(value=value) for value in row[2:])
|
||||
rendered_rows.append(ROW_TEMPLATE.format(indented=indented, filename=filename, cells=cells))
|
||||
previous_group_id = row[0]
|
||||
rendered_rows = ''.join(rendered_rows)
|
||||
# The main template can't use format because the css code uses {}
|
||||
content = MAIN_TEMPLATE.replace('$colheaders', colheaders).replace('$rows', rendered_rows)
|
||||
@ -134,3 +141,9 @@ def export_to_xhtml(colnames, rows):
|
||||
fp.write(content)
|
||||
fp.close()
|
||||
return destpath
|
||||
|
||||
def export_to_csv(dest, colnames, rows):
|
||||
writer = csv.writer(open(dest, 'wt', encoding='utf-8'))
|
||||
writer.writerow(["Group ID"] + colnames)
|
||||
for row in rows:
|
||||
writer.writerow(row)
|
||||
|
@ -274,3 +274,7 @@ class DupeGuru(QObject):
|
||||
flags = QFileDialog.ShowDirsOnly
|
||||
return QFileDialog.getExistingDirectory(self.resultWindow, prompt, '', flags)
|
||||
|
||||
def select_dest_file(self, prompt, extension):
|
||||
files = tr("{} file (*.{})").format(extension.upper(), extension)
|
||||
return QFileDialog.getSaveFileName(self.resultWindow, prompt, '', files)
|
||||
|
||||
|
@ -60,7 +60,8 @@ class ResultWindow(QMainWindow):
|
||||
('actionMarkNone', 'Ctrl+Shift+A', '', tr("Mark None"), self.markNoneTriggered),
|
||||
('actionInvertMarking', 'Ctrl+Alt+A', '', tr("Invert Marking"), self.markInvertTriggered),
|
||||
('actionMarkSelected', '', '', tr("Mark Selected"), self.markSelectedTriggered),
|
||||
('actionExport', '', '', tr("Export To HTML"), self.exportTriggered),
|
||||
('actionExportToHTML', '', '', tr("Export To HTML"), self.app.model.export_to_xhtml),
|
||||
('actionExportToCSV', '', '', tr("Export To CSV"), self.app.model.export_to_csv),
|
||||
('actionSaveResults', 'Ctrl+S', '', tr("Save Results..."), self.saveResultsTriggered),
|
||||
('actionInvokeCustomCommand', 'Ctrl+Alt+I', '', tr("Invoke Custom Command"), self.app.invokeCustomCommand),
|
||||
]
|
||||
@ -115,7 +116,8 @@ class ResultWindow(QMainWindow):
|
||||
self.menuHelp.addAction(self.app.actionOpenDebugLog)
|
||||
self.menuHelp.addAction(self.app.actionAbout)
|
||||
self.menuFile.addAction(self.actionSaveResults)
|
||||
self.menuFile.addAction(self.actionExport)
|
||||
self.menuFile.addAction(self.actionExportToHTML)
|
||||
self.menuFile.addAction(self.actionExportToCSV)
|
||||
self.menuFile.addSeparator()
|
||||
self.menuFile.addAction(self.app.actionQuit)
|
||||
|
||||
@ -231,11 +233,6 @@ class ResultWindow(QMainWindow):
|
||||
def detailsTriggered(self):
|
||||
self.app.show_details()
|
||||
|
||||
def exportTriggered(self):
|
||||
exported_path = self.app.model.export_to_xhtml()
|
||||
url = QUrl.fromLocalFile(exported_path)
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
def makeReferenceTriggered(self):
|
||||
self.app.model.make_selected_reference()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user