2009-06-01 09:55:11 +00:00
|
|
|
# Created By: Virgil Dupras
|
|
|
|
# Created On: 2009-04-25
|
2010-01-01 20:11:34 +00:00
|
|
|
# Copyright 2010 Hardcoded Software (http://www.hardcoded.net)
|
2009-08-05 08:59:46 +00:00
|
|
|
#
|
|
|
|
# This software is licensed under the "HS" License as described in the "LICENSE" file,
|
|
|
|
# which should be included with this package. The terms are also available at
|
|
|
|
# http://www.hardcoded.net/licenses/hs_license
|
2009-06-01 09:55:11 +00:00
|
|
|
|
2009-10-02 11:12:25 +00:00
|
|
|
from __future__ import unicode_literals
|
|
|
|
|
2009-06-01 09:55:11 +00:00
|
|
|
import logging
|
2009-10-13 14:59:19 +00:00
|
|
|
import os
|
2009-06-01 09:55:11 +00:00
|
|
|
import os.path as op
|
|
|
|
|
2010-02-12 14:39:29 +00:00
|
|
|
from PyQt4.QtCore import QTimer, QObject, QCoreApplication, QUrl, SIGNAL
|
|
|
|
from PyQt4.QtGui import QDesktopServices, QFileDialog, QDialog, QMessageBox
|
2009-06-01 09:55:11 +00:00
|
|
|
|
2010-07-13 06:16:44 +00:00
|
|
|
from hscommon import job
|
2010-07-13 06:08:18 +00:00
|
|
|
from hscommon.reg import RegistrationRequired
|
2009-06-01 09:55:11 +00:00
|
|
|
|
2009-12-30 10:37:57 +00:00
|
|
|
from core.app import DupeGuru as DupeGuruBase, JOB_SCAN, JOB_LOAD, JOB_MOVE, JOB_COPY, JOB_DELETE
|
2009-08-31 12:04:43 +00:00
|
|
|
|
2009-10-30 11:41:14 +00:00
|
|
|
from qtlib.about_box import AboutBox
|
2009-09-27 08:44:06 +00:00
|
|
|
from qtlib.progress import Progress
|
2009-10-30 11:41:14 +00:00
|
|
|
from qtlib.reg import Registration
|
2009-06-01 09:55:11 +00:00
|
|
|
|
2009-09-27 08:44:06 +00:00
|
|
|
from . import platform
|
|
|
|
|
|
|
|
from .main_window import MainWindow
|
|
|
|
from .directories_dialog import DirectoriesDialog
|
2010-04-12 13:29:56 +00:00
|
|
|
from .problem_dialog import ProblemDialog
|
2009-06-01 09:55:11 +00:00
|
|
|
|
|
|
|
JOBID2TITLE = {
|
|
|
|
JOB_SCAN: "Scanning for duplicates",
|
|
|
|
JOB_LOAD: "Loading",
|
|
|
|
JOB_MOVE: "Moving",
|
|
|
|
JOB_COPY: "Copying",
|
|
|
|
JOB_DELETE: "Sending files to the recycle bin",
|
|
|
|
}
|
|
|
|
|
|
|
|
def demo_method(method):
|
|
|
|
def wrapper(self, *args, **kwargs):
|
|
|
|
try:
|
|
|
|
return method(self, *args, **kwargs)
|
|
|
|
except RegistrationRequired:
|
|
|
|
msg = "The demo version of dupeGuru only allows 10 actions (delete/move/copy) per session."
|
|
|
|
QMessageBox.information(self.main_window, 'Demo', msg)
|
|
|
|
|
|
|
|
return wrapper
|
|
|
|
|
|
|
|
class DupeGuru(DupeGuruBase, QObject):
|
|
|
|
LOGO_NAME = '<replace this>'
|
|
|
|
NAME = '<replace this>'
|
|
|
|
DELTA_COLUMNS = frozenset()
|
|
|
|
|
|
|
|
def __init__(self, data_module, appid):
|
|
|
|
appdata = unicode(QDesktopServices.storageLocation(QDesktopServices.DataLocation))
|
2009-10-13 14:59:19 +00:00
|
|
|
if not op.exists(appdata):
|
|
|
|
os.makedirs(appdata)
|
2009-10-02 11:12:25 +00:00
|
|
|
# For basicConfig() to work, we have to be sure that no logging has taken place before this call.
|
|
|
|
logging.basicConfig(filename=op.join(appdata, 'debug.log'), level=logging.WARNING)
|
2009-06-01 09:55:11 +00:00
|
|
|
DupeGuruBase.__init__(self, data_module, appdata, appid)
|
|
|
|
QObject.__init__(self)
|
|
|
|
self._setup()
|
|
|
|
|
|
|
|
#--- Private
|
|
|
|
def _setup(self):
|
|
|
|
self.prefs = self._create_preferences()
|
|
|
|
self.prefs.load()
|
|
|
|
self._update_options()
|
|
|
|
self.main_window = self._create_main_window()
|
|
|
|
self._progress = Progress(self.main_window)
|
|
|
|
self.directories_dialog = DirectoriesDialog(self.main_window, self)
|
|
|
|
self.details_dialog = self._create_details_dialog(self.main_window)
|
2010-04-12 13:29:56 +00:00
|
|
|
self.problemDialog = ProblemDialog(parent=self.main_window, app=self)
|
2009-06-01 09:55:11 +00:00
|
|
|
self.preferences_dialog = self._create_preferences_dialog(self.main_window)
|
|
|
|
self.about_box = AboutBox(self.main_window, self)
|
|
|
|
|
|
|
|
self.reg = Registration(self)
|
|
|
|
self.set_registration(self.prefs.registration_code, self.prefs.registration_email)
|
|
|
|
if not self.registered:
|
2009-10-01 09:45:49 +00:00
|
|
|
# The timer scheme is because if the nag is not shown before the application is
|
|
|
|
# completely initialized, the nag will be shown before the app shows up in the task bar
|
|
|
|
# In some circumstances, the nag is hidden by other window, which may make the user think
|
|
|
|
# that the application haven't launched.
|
|
|
|
self._nagTimer = QTimer()
|
|
|
|
self.connect(self._nagTimer, SIGNAL('timeout()'), self.mustShowNag)
|
|
|
|
self._nagTimer.start(0)
|
2009-06-01 09:55:11 +00:00
|
|
|
self.main_window.show()
|
|
|
|
self.load()
|
|
|
|
|
|
|
|
self.connect(QCoreApplication.instance(), SIGNAL('aboutToQuit()'), self.application_will_terminate)
|
|
|
|
self.connect(self._progress, SIGNAL('finished(QString)'), self.job_finished)
|
|
|
|
|
|
|
|
def _setup_as_registered(self):
|
|
|
|
self.prefs.registration_code = self.registration_code
|
|
|
|
self.prefs.registration_email = self.registration_email
|
|
|
|
self.main_window.actionRegister.setVisible(False)
|
|
|
|
self.about_box.registerButton.hide()
|
|
|
|
self.about_box.registeredEmailLabel.setText(self.prefs.registration_email)
|
|
|
|
|
|
|
|
def _update_options(self):
|
|
|
|
self.scanner.mix_file_kind = self.prefs.mix_file_kind
|
|
|
|
self.options['escape_filter_regexp'] = self.prefs.use_regexp
|
|
|
|
self.options['clean_empty_dirs'] = self.prefs.remove_empty_folders
|
|
|
|
|
|
|
|
#--- Virtual
|
|
|
|
def _create_details_dialog(self, parent):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def _create_main_window(self):
|
|
|
|
return MainWindow(app=self)
|
|
|
|
|
|
|
|
def _create_preferences(self):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
def _create_preferences_dialog(self, parent):
|
|
|
|
raise NotImplementedError()
|
|
|
|
|
|
|
|
#--- Override
|
2010-02-06 11:36:43 +00:00
|
|
|
@staticmethod
|
|
|
|
def _open_path(path):
|
|
|
|
url = QUrl.fromLocalFile(unicode(path))
|
|
|
|
QDesktopServices.openUrl(url)
|
|
|
|
|
2010-02-06 14:31:35 +00:00
|
|
|
@staticmethod
|
|
|
|
def _reveal_path(path):
|
2010-02-09 14:24:57 +00:00
|
|
|
DupeGuru._open_path(path[:-1])
|
2010-02-06 14:31:35 +00:00
|
|
|
|
2009-06-01 09:55:11 +00:00
|
|
|
def _start_job(self, jobid, func):
|
|
|
|
title = JOBID2TITLE[jobid]
|
|
|
|
try:
|
|
|
|
j = self._progress.create_job()
|
|
|
|
self._progress.run(jobid, title, func, args=(j, ))
|
|
|
|
except job.JobInProgressError:
|
|
|
|
msg = "A previous action is still hanging in there. You can't start a new one yet. Wait a few seconds, then try again."
|
|
|
|
QMessageBox.information(self.main_window, 'Action in progress', msg)
|
|
|
|
|
2010-02-06 11:12:20 +00:00
|
|
|
def add_selected_to_ignore_list(self):
|
|
|
|
dupes = self.without_ref(self.selected_dupes)
|
|
|
|
if not dupes:
|
|
|
|
return
|
|
|
|
title = "Add to Ignore List"
|
|
|
|
msg = "All selected {0} matches are going to be ignored in all subsequent scans. Continue?".format(len(dupes))
|
|
|
|
if self.main_window._confirm(title, msg):
|
|
|
|
DupeGuruBase.add_selected_to_ignore_list(self)
|
2009-06-01 09:55:11 +00:00
|
|
|
|
|
|
|
@demo_method
|
|
|
|
def copy_or_move_marked(self, copy):
|
|
|
|
opname = 'copy' if copy else 'move'
|
|
|
|
title = "Select a directory to {0} marked files to".format(opname)
|
|
|
|
flags = QFileDialog.ShowDirsOnly
|
|
|
|
destination = unicode(QFileDialog.getExistingDirectory(self.main_window, title, '', flags))
|
|
|
|
if not destination:
|
|
|
|
return
|
|
|
|
recreate_path = self.prefs.destination_type
|
|
|
|
DupeGuruBase.copy_or_move_marked(self, copy, destination, recreate_path)
|
|
|
|
|
|
|
|
delete_marked = demo_method(DupeGuruBase.delete_marked)
|
|
|
|
|
2010-02-06 11:44:21 +00:00
|
|
|
def remove_selected(self):
|
|
|
|
dupes = self.without_ref(self.selected_dupes)
|
|
|
|
if not dupes:
|
|
|
|
return
|
|
|
|
title = "Remove duplicates"
|
|
|
|
msg = "You are about to remove {0} files from results. Continue?".format(len(dupes))
|
|
|
|
if self.main_window._confirm(title, msg):
|
|
|
|
DupeGuruBase.remove_selected(self)
|
|
|
|
|
2010-02-06 11:12:20 +00:00
|
|
|
#--- Public
|
|
|
|
def askForRegCode(self):
|
|
|
|
self.reg.ask_for_code()
|
|
|
|
|
2010-04-13 08:02:09 +00:00
|
|
|
def invokeCustomCommand(self):
|
|
|
|
cmd = self.prefs.custom_command
|
|
|
|
if cmd:
|
|
|
|
self.invoke_command(cmd)
|
|
|
|
else:
|
|
|
|
msg = "You have no custom command set up. Please, set it up in your preferences."
|
|
|
|
QMessageBox.warning(self.main_window, 'Custom Command', msg)
|
|
|
|
|
2009-10-02 11:12:25 +00:00
|
|
|
def openDebugLog(self):
|
|
|
|
debugLogPath = op.join(self.appdata, 'debug.log')
|
2010-02-06 11:36:43 +00:00
|
|
|
self._open_path(debugLogPath)
|
2009-06-01 09:55:11 +00:00
|
|
|
|
|
|
|
def show_about_box(self):
|
|
|
|
self.about_box.show()
|
|
|
|
|
|
|
|
def show_details(self):
|
|
|
|
self.details_dialog.show()
|
|
|
|
|
|
|
|
def show_directories(self):
|
|
|
|
self.directories_dialog.show()
|
|
|
|
|
|
|
|
def show_help(self):
|
2010-04-07 16:04:58 +00:00
|
|
|
base_path = platform.HELP_PATH.format(self.EDITION)
|
|
|
|
url = QUrl.fromLocalFile(op.abspath(op.join(base_path, 'intro.htm')))
|
2009-06-01 09:55:11 +00:00
|
|
|
QDesktopServices.openUrl(url)
|
|
|
|
|
|
|
|
def show_preferences(self):
|
|
|
|
self.preferences_dialog.load()
|
|
|
|
result = self.preferences_dialog.exec_()
|
|
|
|
if result == QDialog.Accepted:
|
|
|
|
self.preferences_dialog.save()
|
|
|
|
self.prefs.save()
|
|
|
|
self._update_options()
|
|
|
|
|
|
|
|
#--- Events
|
|
|
|
def application_will_terminate(self):
|
2009-06-07 07:18:59 +00:00
|
|
|
self.save()
|
2009-06-07 07:17:56 +00:00
|
|
|
self.save_ignore_list()
|
2009-06-01 09:55:11 +00:00
|
|
|
|
2009-10-01 09:45:49 +00:00
|
|
|
def mustShowNag(self):
|
|
|
|
self._nagTimer.stop() # must be shown only once
|
|
|
|
self.reg.show_nag()
|
|
|
|
|
2009-06-01 09:55:11 +00:00
|
|
|
def job_finished(self, jobid):
|
2010-02-12 14:39:29 +00:00
|
|
|
self._job_completed(jobid)
|
2010-04-12 13:29:56 +00:00
|
|
|
if jobid in (JOB_MOVE, JOB_COPY, JOB_DELETE):
|
|
|
|
if self.results.problems:
|
|
|
|
self.problemDialog.show()
|
|
|
|
else:
|
|
|
|
msg = "All files were processed successfully."
|
|
|
|
QMessageBox.information(self.main_window, 'Operation Complete', msg)
|
2010-01-13 09:39:27 +00:00
|
|
|
elif jobid == JOB_SCAN:
|
|
|
|
if not self.results.groups:
|
|
|
|
title = "Scanning complete"
|
|
|
|
msg = "No duplicates found."
|
|
|
|
QMessageBox.information(self.main_window, title, msg)
|
2009-06-01 09:55:11 +00:00
|
|
|
|