~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/plugins/duplicate_finder/recipeMerger.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2009-02-10 17:34:51 UTC
  • mfrom: (2.1.4 squeeze)
  • Revision ID: james.westby@ubuntu.com-20090210173451-bt0a6j0ut71oxqes
Tags: 0.14.5-1
* new upstream release
* 0.14.5 no longer conflicts with newer versions of python-pysqlite and
  python-sqlalchemy.  Relax dependencies accordingly.
* all patches have been pushed upstream, so they can be dropped in Debian.
  The manpage was forgotten when preparing the tarball upstream, so it
  will stick around for another release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
This module contains code for handling the 'merging' of duplicate
4
4
recipes.
5
5
"""
6
 
import gtk, os.path, time
 
6
import gtk, pango, os.path, time
7
7
import gourmet.recipeIdentifier
8
 
from gourmet.gtk_extras import ratingWidget, mnemonic_manager
 
8
from gourmet.gtk_extras import ratingWidget, mnemonic_manager, dialog_extras
9
9
import gourmet.recipeIdentifier
10
10
import gourmet.convert as convert
11
11
import gourmet.gglobals as gglobals
 
12
import gourmet.recipeManager
12
13
from gettext import gettext as _
13
14
 
 
15
NEWER = 1
 
16
OLDER = 2
 
17
 
14
18
try:
15
19
    current_path = os.path.split(os.path.join(os.getcwd(),__file__))[0]
16
20
except:
17
21
    current_path = ''
18
22
 
 
23
def time_to_text (val):
 
24
    curtime = time.time()
 
25
    if val == 0:
 
26
        return 'Unknown'
 
27
    # within 18 hours, return in form 4 hours 23 minutes ago or some such    
 
28
    if curtime - val < 18 * 60 * 60:
 
29
        return _("%s ago")%convert.seconds_to_timestring(curtime-val,round_at=1)
 
30
    tupl=time.localtime(val)
 
31
    if curtime - val <  7 * 24 * 60 * 60:
 
32
        return time.strftime('%A %T',tupl)
 
33
    else:
 
34
        return time.strftime('%D %T',tupl)
 
35
       
 
36
    
19
37
class ConflictError (ValueError):
20
38
    def __init__ (self, conflicts):
21
39
        self.conflicts = conflicts
34
52
    DUP_INDEX_PAGE = 0
35
53
    MERGE_PAGE = 1
36
54
    
37
 
    def __init__ (self, rd, in_recipes=None, on_close_callback=None):
38
 
        self.rd = rd
 
55
    def __init__ (self, rd=None, in_recipes=None, on_close_callback=None):
 
56
        if rd:
 
57
            self.rd = rd
 
58
        else:
 
59
            self.rd = gourmet.recipeManager.get_recipe_manager()
39
60
        self.in_recipes = in_recipes
40
61
        self.on_close_callback = on_close_callback
41
62
        self.to_merge = [] # Queue of recipes to be merged...
53
74
            'on_cancelMergeButton_clicked':self.cancel_merge,
54
75
            'on_mergeSelectedButton_clicked':self.merge_selected,
55
76
            'on_applyButton_clicked':self.apply_merge,
 
77
            'auto_merge':self.offer_auto_merge,
56
78
            'close':self.close,
57
79
            }
58
80
            )
62
84
            'recipeDiffScrolledWindow',
63
85
            'duplicateRecipeTreeView',
64
86
            'mergeAllButton','mergeSelectedButton', # buttons on list-dups page (minus close button)
65
 
            'applyMergeButton','cancelMergeButton', # buttons on merge-recs page
66
 
            'searchTypeCombo','includeDeletedRecipesCheckButton','notebook'
 
87
            'applyMergeButton','closeMergeButton','cancelMergeButton', # buttons on merge-recs page
 
88
            'searchTypeCombo','includeDeletedRecipesCheckButton','notebook',
 
89
            'mergeInfoLabel'
67
90
            ]:
68
91
            setattr(self,w,self.glade.get_widget(w))
69
92
        self.setup_treeview()
74
97
        self.duplicateRecipeTreeView.append_column(col)
75
98
        self.duplicateRecipeTreeView.insert_column_with_data_func(
76
99
            -1, # position
77
 
            'Last Modified', # title
78
 
            renderer, # renderer
79
 
            self.time_cell_data_func, # function
80
 
            3 # data column
81
 
            )
82
 
        self.duplicateRecipeTreeView.append_column(col)
 
100
             'Last Modified', # title
 
101
             renderer, # renderer
 
102
             self.time_cell_data_func, # function
 
103
             3 # data column
 
104
             )
83
105
        col = gtk.TreeViewColumn('Duplicates',renderer,text=4)
84
106
        self.duplicateRecipeTreeView.append_column(col)
85
107
        self.duplicateRecipeTreeView.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
88
110
        """Display time in treeview cell.
89
111
        """
90
112
        val = model.get_value(titer,data_col)
91
 
        curtime = time.time()
92
 
        # within 18 hours, return in form 4 hours 23 minutes ago or some such
93
 
        if val == 0:
94
 
            cell.set_property('text',_('Unknown'))
95
 
            return
96
 
        if curtime - val < 18 * 60 * 60:
97
 
            cell.set_property('text',
98
 
                              _("%s ago")%convert.seconds_to_timestring(curtime-val,round_at=1))
99
 
            return
100
 
        tupl=time.localtime(val)
101
 
        if curtime - val <  7 * 24 * 60 * 60:
102
 
            cell.set_property('text',
103
 
                              time.strftime('%A %T',tupl))
104
 
            return
105
 
        else:
106
 
            cell.set_property('text',
107
 
                              time.strftime('%D %T',tupl))
108
 
            return
 
113
        cell.set_property('text',time_to_text(val))
109
114
 
110
115
    def populate_tree (self):
111
116
        """Populate treeview with duplicate recipes.
137
142
            r = self.rd.get_rec(first)
138
143
            firstIter = self.treeModel.append(
139
144
                None,
140
 
                (dup_index, first, r.title, r.last_modified or 0, str(nduplicates))
 
145
                (dup_index or 0, first or 0, r.title or '', r.last_modified or 0, str(nduplicates))
141
146
                )
142
147
            for o in others:
143
148
                r = self.rd.get_rec(o)
147
152
    def merge_next_recipe (self, ):
148
153
        if self.to_merge:
149
154
            self.current_dup_index = self.to_merge.pop(0)
 
155
            self.mergeInfoLabel.set_text(
 
156
                'Merging recipe %(index)s of %(total)s'%{
 
157
                    'index':self.total_to_merge - len(self.to_merge),
 
158
                    'total':self.total_to_merge
 
159
                    })
150
160
            duplicate_recipes = self.dups[self.current_dup_index]
151
161
            #self.idt = IngDiffTable(self.rd,duplicate_recipes[0],duplicate_recipes[1])
152
162
            self.current_recs = [self.rd.get_rec(i) for i in duplicate_recipes]
 
163
            last_modified = {'last_modified':[r.last_modified for r in self.current_recs]}
153
164
            self.current_diff_data = gourmet.recipeIdentifier.diff_recipes(self.rd,self.current_recs)
154
 
            self.diff_table = DiffTable(self.current_diff_data,self.current_recs[0],parent=self.recipeDiffScrolledWindow)
 
165
            last_modified.update(self.current_diff_data)            
 
166
            self.diff_table = DiffTable(last_modified,self.current_recs[0],parent=self.recipeDiffScrolledWindow)
155
167
            self.diff_table.add_ingblocks(self.rd, self.current_recs)
156
168
            if not self.diff_table.idiffs and not self.current_diff_data:
157
169
                # If there are no differences, just merge the recipes...
201
213
        for d in dup_indices:
202
214
            if d not in self.to_merge:
203
215
                self.to_merge.append(d)
 
216
        self.total_to_merge = len(self.to_merge)
204
217
        self.merge_next_recipe()
205
218
        
206
219
    def merge_all (self, *args):
207
220
        """Merge all rows currently in treeview.
208
221
        """
209
 
        #print 'CALL: merge_all'
210
 
        self.to_merge = range(len(self.dups))
 
222
        self.total_to_merge = len(self.dups)
 
223
        self.to_merge = range(self.total_to_merge)
211
224
        self.merge_next_recipe()
212
225
 
 
226
    def offer_auto_merge (self, *args):
 
227
        try:
 
228
            option =dialog_extras.getOption(
 
229
                label=_('Auto-Merge recipes'),
 
230
                options=[
 
231
                    (_('Always use newest recipe'),NEWER),
 
232
                    (_('Always use oldest recipe'),OLDER),
 
233
                    # The following would be nice to add eventually...
 
234
                    #_('Always use longer field'),
 
235
                    #_('Ignore differences in ingredient keys')
 
236
                    ]
 
237
                )
 
238
            if not option:
 
239
                return
 
240
            self.do_auto_merge(NEWER)
 
241
        except dialog_extras.UserCancelledError:
 
242
            pass
 
243
 
 
244
    def do_auto_merge (self, mode):
 
245
        if self.recipeDiffScrolledWindow.get_child():
 
246
            self.recipeDiffScrolledWindow.remove(self.recipeDiffScrolledWindow.get_child())        
 
247
        vb = gtk.VBox()
 
248
        l = gtk.Label()
 
249
        l.set_markup('<u>Automatically merged recipes</u>')
 
250
        vb.pack_start(l,expand=False,fill=False); vb.show_all()
 
251
        self.recipeDiffScrolledWindow.add_with_viewport(vb)
 
252
        def do_auto_merge ():
 
253
            kept = self.auto_merge_current_rec(mode)
 
254
            label = gtk.Label('%s'%kept.title)
 
255
            vb.pack_start(label,expand=False,fill=False); label.show()
 
256
        self.cancelMergeButton.hide()
 
257
        self.applyMergeButton.hide()
 
258
        self.closeMergeButton.set_sensitive(False)
 
259
        do_auto_merge()
 
260
        while self.to_merge:
 
261
            self.mergeInfoLabel.set_text(
 
262
                'Automatically merging recipe %(index)s of %(total)s'%{
 
263
                    'index':self.total_to_merge - len(self.to_merge),
 
264
                    'total':self.total_to_merge
 
265
                    })            
 
266
            self.current_dup_index = self.to_merge.pop(0)
 
267
            duplicate_recipes = self.dups[self.current_dup_index]            
 
268
            self.current_recs = [self.rd.get_rec(i) for i in duplicate_recipes]
 
269
            do_auto_merge()
 
270
            while gtk.events_pending(): gtk.main_iteration()
 
271
        self.mergeInfoLabel.set_text('Automatically merged %s recipes'%self.total_to_merge)
 
272
        self.closeMergeButton.set_sensitive(True)           
 
273
        
 
274
    def auto_merge_current_rec (self, mode):
 
275
        def compare_recs (r1, r2):
 
276
            result = cmp(r1.last_modified,r2.last_modified)
 
277
            if mode==NEWER: return result
 
278
            else: return -result
 
279
        self.current_recs.sort(compare_recs)
 
280
        keeper = self.current_recs[0]
 
281
        tossers = self.current_recs[1:]
 
282
        for to_toss in tossers:
 
283
            self.rd.delete_rec(to_toss)
 
284
        return keeper
 
285
        
213
286
    def cancel_merge (self, *args):
214
287
        self.merge_next_recipe()
215
288
        if not self.to_merge:
256
329
 
257
330
    def autoMergeRecipes (self, recs):
258
331
        to_fill,conflicts = gourmet.recipeIdentifier.merge_recipes(self.rd,
259
 
                                                           recs)
 
332
                                                                   recs)
260
333
        if conflicts:
261
334
            raise ConflictError(conflicts)
262
335
        else:
265
338
            self.rd.modify_rec(to_keep,to_fill)
266
339
            # Delete the other recipes...
267
340
            for r in recs[1:]:
268
 
                self.rd.delete_rec(r)
 
341
                self.rd.delete_rec(r.id)
269
342
 
270
343
    def uiMergeRecipes (self, recs):
271
344
        diffs = gourmet.recipeIdentifier.diff_recipes(self.rd,
285
358
 
286
359
    recipe_object is a recipe object representing one of our duplicate
287
360
    recs, from which we can grab attributes that are not different.
 
361
 
 
362
    dont_choose is a list of attributes whose differences are
 
363
    displayed, but where no choice is offered (such as modification
 
364
    time for the recipe).
288
365
    """
289
366
 
290
 
    def __init__ (self, diff_dic, recipe_object=None, parent=None):
 
367
    def __init__ (self, diff_dic, recipe_object=None, parent=None,
 
368
                  dont_choose=[]):
291
369
        self.idiffs = []
292
370
        self.diff_dic = diff_dic
293
371
        gtk.Table.__init__(self)
296
374
        self.set_row_spacings(6)        
297
375
        self.row = 0
298
376
        self.max_cols = 1
299
 
        for attr,name,typ in gglobals.REC_ATTRS \
 
377
        for attr,name,typ in [('last_modified','Last Modified',None)] + gglobals.REC_ATTRS \
300
378
                + [('image','Image',None)] \
301
379
                + [(attr,gglobals.TEXT_ATTR_DIC[attr],None) for attr in gglobals.DEFAULT_TEXT_ATTR_ORDER]:
302
380
            if diff_dic.has_key(attr):
414
492
                        l = gtk.Label(txt)
415
493
                        l.set_alignment(0.0,0.0)                    
416
494
                        l.set_use_markup(True)
417
 
                        l.set_line_wrap(True); l.set_line_wrap_mode(gtk.WRAP_WORD)
 
495
                        l.set_line_wrap(True); l.set_line_wrap_mode(pango.WRAP_WORD)
418
496
                        l.show()
419
497
                        self.setup_widget_size(l,in_col=True)
420
498
                        self.attach(l,col+1,col+2,self.row+1+n,self.row+2+n,
430
508
            l = gtk.Label(blocks[0])
431
509
            l.set_alignment(0.0,0.0)
432
510
            l.set_use_markup(True)
433
 
            l.set_line_wrap(True); l.set_line_wrap_mode(gtk.WRAP_WORD)
 
511
            l.set_line_wrap(True); l.set_line_wrap_mode(pango.WRAP_WORD)
434
512
            l.show()
435
513
            self.attach(l,1,5,self.row,self.row+1,xoptions=gtk.SHRINK|gtk.FILL,yoptions=gtk.SHRINK|gtk.FILL)
436
514
        lab.set_alignment(0.0,0.0); lab.show()
478
556
    elif len(t) < 250:
479
557
        l = gtk.Label(t)
480
558
        if use_markup: l.set_use_markup(use_markup)
481
 
        l.set_line_wrap_mode(gtk.WRAP_WORD)
 
559
        l.set_line_wrap_mode(pango.WRAP_WORD)
482
560
        return l
483
561
    else:
484
562
        return put_text_in_scrolled_window(t)
495
573
        return lambda v: (v and gtk.Label("An Image") or gtk.Label("No Image"))
496
574
    elif attribute in gglobals.DEFAULT_TEXT_ATTR_ORDER:        
497
575
        return make_text_label
 
576
    elif attribute == 'last_modified':
 
577
        return lambda v: gtk.Label(time_to_text(v))
498
578
    else:
499
579
        return lambda v: v and gtk.Label(v) or gtk.Label(_('None'))
500
580