mirror of
https://github.com/arsenetar/dupeguru.git
synced 2025-03-09 21:24:36 +00:00
[#138] Added a very preliminary version of the prioritization dialog. A big part of this commit is about refactoring the tests and introducing a TestApp for dupeGuru (in core.tests.base).
This commit is contained in:
parent
ff228035a3
commit
518228a368
@ -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()
|
||||
|
30
core/gui/prioritize_dialog.py
Normal file
30
core/gui/prioritize_dialog.py
Normal file
@ -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]
|
41
core/prioritize.py
Normal file
41
core/prioritize.py
Normal file
@ -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
|
@ -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
|
||||
|
103
core/tests/base.py
Normal file
103
core/tests/base.py
Normal file
@ -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)
|
1
core/tests/conftest.py
Normal file
1
core/tests/conftest.py
Normal file
@ -0,0 +1 @@
|
||||
from hscommon.testutil import pytest_funcarg__app
|
@ -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():
|
||||
|
43
core/tests/prioritize_test.py
Normal file
43
core/tests/prioritize_test.py
Normal file
@ -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'])
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user