diff --git a/core/engine.py b/core/engine.py index 4043e105..898a5082 100644 --- a/core/engine.py +++ b/core/engine.py @@ -223,7 +223,7 @@ def getmatches_by_contents(files, sizeattr='size', partial=False, j=job.nulljob) j.add_progress(desc=tr("%d matches found") % len(result)) return result -class Group(object): +class Group: #---Override def __init__(self): self._clear() diff --git a/core/gui/prioritize_dialog.py b/core/gui/prioritize_dialog.py new file mode 100644 index 00000000..63287c81 --- /dev/null +++ b/core/gui/prioritize_dialog.py @@ -0,0 +1,30 @@ +# Created By: Virgil Dupras +# Created On: 2011-09-06 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" 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/bsd_license + +from hscommon.gui.selectable_list import SelectableList + +from ..prioritize import KindCategory + +class CriterionCategoryList(SelectableList): + def __init__(self, dialog): + self.dialog = dialog + SelectableList.__init__(self, [c.NAME for c in dialog.categories]) + + def _update_selection(self): + self.dialog.select_category(self.dialog.categories[self.selected_index]) + + +class PrioritizeDialog: + def __init__(self, view, app): + self.categories = [KindCategory(app.results)] + self.category_list = CriterionCategoryList(self) + self.criteria_list = [] + + def select_category(self, category): + criteria = category.criteria_list() + self.criteria_list = [c.value for c in criteria] diff --git a/core/prioritize.py b/core/prioritize.py new file mode 100644 index 00000000..a95b3898 --- /dev/null +++ b/core/prioritize.py @@ -0,0 +1,41 @@ +# Created By: Virgil Dupras +# Created On: 2011/09/07 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" 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/bsd_license + +from hscommon.util import dedupe, flatten + +class CriterionCategory: + NAME = "Undefined" + + def __init__(self, results): + self.results = results + + #--- Virtual + def _extract_value(self, dupe): + raise NotImplementedError() + + #--- Public + def criteria_list(self): + dupes = flatten(g[:] for g in self.results.groups) + values = dedupe(self._extract_value(d) for d in dupes) + return [Criterion(self, value) for value in values] + +class Criterion: + def __init__(self, category, value): + self.category = category + self.value = value + + @property + def display(self): + return "{} ({})".format(self.category, self.value) + + +class KindCategory(CriterionCategory): + NAME = "Kind" + + def _extract_value(self, dupe): + return dupe.extension diff --git a/core/tests/app_test.py b/core/tests/app_test.py index edb87dfa..3320fef7 100644 --- a/core/tests/app_test.py +++ b/core/tests/app_test.py @@ -16,36 +16,16 @@ from hscommon.path import Path import hscommon.conflict import hscommon.util from hscommon.testutil import CallLogger, eq_, log_calls -from jobprogress.job import nulljob, Job, JobCancelled +from jobprogress.job import Job -from . import data +from .base import DupeGuru from .results_test import GetTestGroups from .. import app, fs, engine -from ..app import DupeGuru as DupeGuruBase from ..gui.details_panel import DetailsPanel from ..gui.directory_tree import DirectoryTree from ..gui.result_table import ResultTable from ..scanner import ScanType -class DupeGuru(DupeGuruBase): - JOB = nulljob - - def __init__(self): - DupeGuruBase.__init__(self, data, '/tmp') - - def _start_job(self, jobid, func, *args): - try: - func(self.JOB, *args) - except JobCancelled: - return - - def _get_default(self, key_name): - return None - - def _set_default(self, key_name, value): - pass - - def add_fake_files_to_directories(directories, files): directories.get_files = lambda j=None: iter(files) directories._dirs.append('this is just so Scan() doesnt return 3') @@ -185,6 +165,7 @@ class TestCaseDupeGuru_clean_empty_dirs: class TestCaseDupeGuruWithResults: def pytest_funcarg__do_setup(self, request): + # XXX eventually, convert this to TestApp-based tests self.app = DupeGuru() self.objects,self.matches,self.groups = GetTestGroups() self.app.results.groups = self.groups diff --git a/core/tests/base.py b/core/tests/base.py new file mode 100644 index 00000000..87aaeec8 --- /dev/null +++ b/core/tests/base.py @@ -0,0 +1,103 @@ +# Created By: Virgil Dupras +# Created On: 2011/09/07 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" 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/bsd_license + +from hscommon.testutil import TestApp as TestAppBase, eq_, with_app +from hscommon.path import Path +from hscommon.util import get_file_ext +from jobprogress.job import nulljob, JobCancelled + +from .. import engine +from ..engine import getwords +from ..app import DupeGuru as DupeGuruBase +from ..gui.details_panel import DetailsPanel +from ..gui.directory_tree import DirectoryTree +from ..gui.result_table import ResultTable +from ..gui.prioritize_dialog import PrioritizeDialog +from . import data + +class DupeGuru(DupeGuruBase): + JOB = nulljob + + def __init__(self): + DupeGuruBase.__init__(self, data, '/tmp') + + def _start_job(self, jobid, func, *args): + try: + func(self.JOB, *args) + except JobCancelled: + return + + def _get_default(self, key_name): + return None + + def _set_default(self, key_name, value): + pass + +class NamedObject: + def __init__(self, name="foobar", with_words=False, size=1, folder=None): + self.name = name + if folder is None: + folder = Path('basepath') + self._folder = folder + self.size = size + self.md5partial = name + self.md5 = name + if with_words: + self.words = getwords(name) + self.is_ref = False + + def __bool__(self): + return False #Make sure that operations are made correctly when the bool value of files is false. + + @property + def path(self): + return self._folder + self.name + + @property + def folder_path(self): + return self.path[:-1] + + @property + def extension(self): + return get_file_ext(self.name) + +# Returns a group set that looks like that: +# "foo bar" (1) +# "bar bleh" (1024) +# "foo bleh" (1) +# "ibabtu" (1) +# "ibabtu" (1) +def GetTestGroups(): + objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")] + objects[1].size = 1024 + matches = engine.getmatches(objects) #we should have 5 matches + groups = engine.get_groups(matches) #We should have 2 groups + for g in groups: + g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is + groups.sort(key=len, reverse=True) # We want the group with 3 members to be first. + return (objects,matches,groups) + +class TestApp(TestAppBase): + def __init__(self): + make_gui = self.make_gui + self.app = DupeGuru() + # XXX After hscommon.testutil.TestApp has had its default parent changed to seomthing + # customizable (with adjustments in moneyguru) we can get rid of 'parent=' + make_gui('rtable', ResultTable, parent=self.app) + make_gui('dtree', DirectoryTree, parent=self.app) + make_gui('dpanel', DetailsPanel, parent=self.app) + make_gui('pdialog', PrioritizeDialog, parent=self.app) + for elem in [self.rtable, self.dtree, self.dpanel]: + elem.connect() + + #--- Helpers + def select_pri_criterion(self, name): + # Select a main prioritize criterion by name instead of by index. Makes tests more + # maintainable. + index = self.pdialog.category_list.index(name) + self.pdialog.category_list.select(index) diff --git a/core/tests/conftest.py b/core/tests/conftest.py new file mode 100644 index 00000000..8115b8b1 --- /dev/null +++ b/core/tests/conftest.py @@ -0,0 +1 @@ +from hscommon.testutil import pytest_funcarg__app \ No newline at end of file diff --git a/core/tests/engine_test.py b/core/tests/engine_test.py index d9bb7db5..acfaa62e 100644 --- a/core/tests/engine_test.py +++ b/core/tests/engine_test.py @@ -12,19 +12,10 @@ from jobprogress import job from hscommon.util import first from hscommon.testutil import eq_, log_calls +from .base import NamedObject from .. import engine from ..engine import * -class NamedObject: - def __init__(self, name="foobar", with_words=False, size=1): - self.name = name - self.size = size - self.md5partial = name - self.md5 = name - if with_words: - self.words = getwords(name) - - no = NamedObject def get_match_triangle(): diff --git a/core/tests/prioritize_test.py b/core/tests/prioritize_test.py new file mode 100644 index 00000000..41d4dc01 --- /dev/null +++ b/core/tests/prioritize_test.py @@ -0,0 +1,43 @@ +# Created By: Virgil Dupras +# Created On: 2011/09/07 +# Copyright 2011 Hardcoded Software (http://www.hardcoded.net) +# +# This software is licensed under the "BSD" 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/bsd_license + +from itertools import combinations + +from .base import TestApp, NamedObject, with_app, eq_ +from ..engine import Group, Match + +no = NamedObject + +def app_with_dupes(dupes): + # Creates an app with specified dupes. dupes is a list of lists, each list in the list being + # a dupe group. We cheat a little bit by creating dupe groups manually instead of running a + # dupe scan, but it simplifies the test code quite a bit + app = TestApp() + groups = [] + for dupelist in dupes: + g = Group() + for dupe1, dupe2 in combinations(dupelist, 2): + g.add_match(Match(dupe1, dupe2, 100)) + groups.append(g) + app.app.results.groups = groups + app.app._results_changed() + return app + +#--- +def app_normal_results(): + # Just some results, with different extensions and size, for good measure. + dupes = [ + [no('foo1.ext1', size=1), no('foo2.ext2', size=2)], + ] + return app_with_dupes(dupes) + +@with_app(app_normal_results) +def test_kind_subcrit(app): + # The subcriteria of the "Kind" criteria is a list of extensions contained in the dupes. + app.select_pri_criterion("Kind") + eq_(app.pdialog.criteria_list, ['ext1', 'ext2']) \ No newline at end of file diff --git a/core/tests/results_test.py b/core/tests/results_test.py index d726dbec..f611463b 100644 --- a/core/tests/results_test.py +++ b/core/tests/results_test.py @@ -1,6 +1,5 @@ # Created By: Virgil Dupras # Created On: 2006/02/23 -# $Id$ # Copyright 2011 Hardcoded Software (http://www.hardcoded.net) # # This software is licensed under the "BSD" License as described in the "LICENSE" file, @@ -12,42 +11,14 @@ import os.path as op from xml.etree import ElementTree as ET -from hscommon.path import Path from hscommon.testutil import eq_ from hscommon.util import first -from . import engine_test, data +from . import data from .. import engine +from .base import NamedObject, GetTestGroups from ..results import Results -class NamedObject(engine_test.NamedObject): - path = property(lambda x:Path('basepath') + x.name) - is_ref = False - - def __bool__(self): - return False #Make sure that operations are made correctly when the bool value of files is false. - - @property - def folder_path(self): - return self.path[:-1] - - -# Returns a group set that looks like that: -# "foo bar" (1) -# "bar bleh" (1024) -# "foo bleh" (1) -# "ibabtu" (1) -# "ibabtu" (1) -def GetTestGroups(): - objects = [NamedObject("foo bar"),NamedObject("bar bleh"),NamedObject("foo bleh"),NamedObject("ibabtu"),NamedObject("ibabtu")] - objects[1].size = 1024 - matches = engine.getmatches(objects) #we should have 5 matches - groups = engine.get_groups(matches) #We should have 2 groups - for g in groups: - g.prioritize(lambda x:objects.index(x)) #We want the dupes to be in the same order as the list is - groups.sort(key=len, reverse=True) # We want the group with 3 members to be first. - return (objects,matches,groups) - class TestCaseResultsEmpty: def setup_method(self, method): self.results = Results(data)