Merge lp://qastaging/~pulb/cgmail/junkfilter_fixes into lp://qastaging/cgmail

Proposed by Patrick Ulbrich
Status: Merged
Merged at revision: not available
Proposed branch: lp://qastaging/~pulb/cgmail/junkfilter_fixes
Merge into: lp://qastaging/cgmail
Diff against target: None lines
To merge this branch: bzr merge lp://qastaging/~pulb/cgmail/junkfilter_fixes
Reviewer Review Type Date Requested Status
Francesco Marella Approve
Review via email: mp+11604@code.qastaging.launchpad.net
To post a comment you must log in.
Revision history for this message
Francesco Marella (francesco-marella) wrote :

great work pat! merging into trunk.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cGmail/lib/junkfilter.py'
2--- cGmail/lib/junkfilter.py 2009-08-21 09:28:14 +0000
3+++ cGmail/lib/junkfilter.py 2009-09-08 01:04:33 +0000
4@@ -17,12 +17,16 @@
5 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
6
7 import threading
8+import os
9+from stat import *
10 from xml.etree.ElementTree import ElementTree, Element, SubElement, parse
11 from email.Utils import parseaddr
12 from common import *
13
14 from utils import log
15
16+DEFAULT_FILTER_FILE = os.path.expanduser("~/.config/cgmail/junkfilters.xml")
17+
18 class CompareType:
19 """CompareType enumeration."""
20 STARTS_WITH = 1
21@@ -75,15 +79,36 @@
22 class JunkFilterManager:
23 """Manages all junkmail filtering."""
24
25+ # Default instance.
26+ __default = None
27+
28+
29 def __init__(self, filename, create = False):
30- self.__lock = threading.Lock()
31- self.__filename = filename
32- self.__filters = []
33+ self.__lock = threading.Lock()
34+ self.__filename = filename
35+ self.__file_last_modified = 0
36+ self.__filters = []
37
38 if create:
39 self.__create_filter_file()
40
41- self.reload_filters()
42+ self.reload_filters(True)
43+
44+
45+ @staticmethod
46+ def get_default():
47+ """
48+ Returns the default JunkFilterManager instance.
49+ """
50+ if not JunkFilterManager.__default:
51+ if os.path.exists(DEFAULT_FILTER_FILE):
52+ create = False
53+ else:
54+ create = True
55+
56+ JunkFilterManager.__default = JunkFilterManager(DEFAULT_FILTER_FILE, create)
57+
58+ return JunkFilterManager.__default
59
60
61 def filter(self, mails):
62@@ -132,11 +157,21 @@
63 return False
64
65
66- def reload_filters(self):
67+ def reload_filters(self, force = True):
68 """
69 Loads the filters of the XML file
70 specified in the constructor.
71+
72+ When force is set to False, filters are reloaded
73+ only when the filterfile has been changed.
74 """
75+ modified = os.stat(self.__filename)[ST_MTIME]
76+
77+ if (not force) and (modified == self.__file_last_modified):
78+ return
79+
80+ self.__file_last_modified = modified
81+
82 log("loading junkmail filters from '%s'" % self.__filename)
83 self.clear_filters()
84 tree = parse(self.__filename)
85@@ -200,6 +235,12 @@
86
87 def __create_filter_file(self):
88 log("creating junkmail filterfile '%s'" % self.__filename)
89+
90+ # Create path to filterfile if necessary.
91+ dir_name = os.path.dirname(self.__filename)
92+ if len(dir_name) and (not os.path.exists(dir_name)):
93+ os.makedirs(dir_name)
94+
95 root = Element("junkfilters")
96 ElementTree(root).write(self.__filename)
97
98@@ -223,11 +264,7 @@
99
100 # tests:
101 if __name__ == "__main__":
102- FILE_PATH = os.path.expanduser("~/.config/cgmail")
103- if not os.path.exists(FILE_PATH):
104- os.makedirs(FILE_PATH)
105-
106- m = JunkFilterManager(os.path.join(FILE_PATH, "junkfilters.xml"));
107+ m = JunkFilterManager(os.path.expanduser("~/junkfilters_test.xml"), True);
108 print m.match_filters([u"späm!","me@spamer.com","1"])
109 print m.match_filters(["none","zulu <zulu99@gmx.net>","3"])
110 print m.filter([["nospam","guy@domain.com", "5"], [u"späm!","guy@domain.com","3"], ["subject", "spamer@1und1.de", "8"]])
111
112=== modified file 'cGmail/manager/junkfilterdialog.py'
113--- cGmail/manager/junkfilterdialog.py 2009-08-26 07:32:43 +0000
114+++ cGmail/manager/junkfilterdialog.py 2009-09-10 21:59:40 +0000
115@@ -37,36 +37,34 @@
116 def __init__(self):
117 self.filters_changed = False
118
119- # initialize junkfilter manager
120- filter_file = os.path.expanduser("~/.config/cgmail/junkfilters.xml")
121- if os.path.exists(filter_file):
122- create = False
123- else:
124- create = True
125-
126- self.__filter_man = JunkFilterManager(filter_file, create)
127-
128- # build popup menu
129+ # Instanciate junkfilter manager.
130+ self.__filter_man = JunkFilterManager.get_default()
131+
132+ # Build popup menu.
133 self.builder2 = gtk.Builder()
134 self.builder2.add_from_file(POPUPMENU_UI_FILE)
135 self.builder2.set_translation_domain("cgmail")
136
137 dict = {
138- "on_remove_activate": self.on_remove_activate
139+ "on_top_activate" : self.on_top_activate,
140+ "on_up_activate" : self.on_up_activate,
141+ "on_down_activate" : self.on_down_activate,
142+ "on_bottom_activate" : self.on_bottom_activate,
143+ "on_remove_activate" : self.on_remove_activate
144 }
145 self.builder2.connect_signals(dict)
146 self.popupmenu = self.builder2.get_object("menu")
147
148- # build gui
149+ # Build gui.
150 self.builder = gtk.Builder()
151 self.builder.add_from_file(UI_FILE)
152 self.builder.set_translation_domain("cgmail")
153
154 dict = {
155- "on_add_button_clicked": self.on_add_button_clicked,
156- "on_filters_treeview_button_press" : self.on_filters_treeview_button_press,
157- "on_string_entry_key_press" : self.on_string_entry_key_press,
158- "on_filters_treeview_key_press" : self.on_filters_treeview_key_press
159+ "on_add_button_clicked" : self.on_add_button_clicked,
160+ "on_filters_treeview_button_press" : self.on_filters_treeview_button_press,
161+ "on_string_entry_key_press" : self.on_string_entry_key_press,
162+ "on_filters_treeview_key_press" : self.on_filters_treeview_key_press
163 }
164 self.builder.connect_signals(dict)
165
166@@ -78,7 +76,7 @@
167 self.add_button = self.builder.get_object("add_button")
168
169
170- # filters treeview
171+ # Filters treeview.
172 self.filters_store = gtk.ListStore(
173 gobject.TYPE_BOOLEAN,
174 gobject.TYPE_STRING,
175@@ -108,29 +106,36 @@
176
177
178 textcell = gtk.CellRendererText()
179- # mailfield combobox
180+ # Mailfield combobox.
181 self.mailfield_box.set_model(gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT))
182 self.mailfield_box.pack_end(textcell, True)
183 self.mailfield_box.add_attribute(textcell, 'text', 0)
184
185- # comparetype combobox
186+ # Comparetype combobox.
187 self.comparetype_box.set_model(gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_INT))
188 self.comparetype_box.pack_end(textcell, True)
189 self.comparetype_box.add_attribute(textcell, 'text', 0)
190
191
192- # fill in data
193+ # Fill in data.
194 self.__fill_filters()
195 self.__fill_mailfields()
196 self.__fill_comparetypes()
197
198+
199+ # Additional signal handlers.
200+ # Make sure they are connected _after_ gui creation!
201+
202+ # Connect signal handler for row reordering.
203+ self.filters_store.connect("row-inserted", self.on_row_inserted)
204+
205 self.run()
206
207 def run(self):
208 result = self.dialog.run()
209
210 if self.filters_changed:
211- self.__filter_man.save_filters()
212+ self.__save_filters()
213
214 self.dialog.destroy()
215
216@@ -150,6 +155,55 @@
217 def on_add_button_clicked(self, butt):
218 self.__add_new_filter()
219
220+ def on_top_activate(self, arg):
221+ selection = self.filters_treeview.get_selection()
222+ model, selected = selection.get_selected()
223+ assert not selected is None
224+ model.move_after(selected, None)
225+
226+ self.filters_changed = True
227+
228+ def on_up_activate(self, arg):
229+ selection = self.filters_treeview.get_selection()
230+ model, selected = selection.get_selected()
231+ assert not selected is None
232+ path = model.get_path(selected)
233+ pos = path[-1]
234+ if pos == 0:
235+ return
236+
237+ prev_path = list(path)[:-1]
238+ prev_path.append(pos - 1)
239+ prev = model.get_iter(tuple(prev_path))
240+
241+ model.swap(selected, prev)
242+
243+ self.filters_changed = True
244+
245+ def on_down_activate(self, arg):
246+ selection = self.filters_treeview.get_selection()
247+ model, selected = selection.get_selected()
248+ assert not selected is None
249+ next = model.iter_next(selected)
250+ if next is None:
251+ return
252+
253+ model.swap(selected, next)
254+
255+ self.filters_changed = True
256+
257+ def on_bottom_activate(self, arg):
258+ selection = self.filters_treeview.get_selection()
259+ model, selected = selection.get_selected()
260+ assert not selected is None
261+
262+ model.move_before(selected, None)
263+
264+ self.filters_changed = True
265+
266+ def on_row_inserted(self, model, path, iter):
267+ self.filters_changed = True
268+
269 def on_remove_activate(self, arg):
270 self.__remove_filter()
271
272@@ -217,8 +271,6 @@
273 comparetype,
274 strn)
275
276- self.__filter_man.add_filter(f)
277-
278 self.__append_filter(f)
279
280 self.string_entry.set_text("")
281@@ -233,12 +285,7 @@
282 return
283
284 path = path_list[0]
285-
286 iter = model.get_iter(path)
287- f = model.get_value(iter, FILTER_COL)
288-
289- self.__filter_man.remove_filter(f)
290-
291 model.remove(iter)
292
293 self.filters_changed = True
294@@ -256,4 +303,12 @@
295 self.filters_store.set_value(iter, STRING_COL, f.string)
296 self.filters_store.set_value(iter, FILTER_COL, f)
297
298+ def __save_filters(self):
299+ self.__filter_man.clear_filters()
300+
301+ for row in self.filters_store:
302+ f = row[FILTER_COL]
303+ self.__filter_man.add_filter(f)
304+
305+ self.__filter_man.save_filters()
306
307
308=== modified file 'cGmail/service/checkersrunner.py'
309--- cGmail/service/checkersrunner.py 2009-09-01 19:01:53 +0000
310+++ cGmail/service/checkersrunner.py 2009-09-08 00:19:10 +0000
311@@ -48,14 +48,8 @@
312 self.checkers = {} # account_id: checker
313 self.checking = False
314
315- # initialize junkfilter manager
316- filter_file = os.path.expanduser("~/.config/cgmail/junkfilters.xml")
317- if os.path.exists(filter_file):
318- create = False
319- else:
320- create = True
321-
322- self.__filter_man = JunkFilterManager(filter_file, create)
323+ # instanciate junkfilter manager
324+ self.__filter_man = JunkFilterManager.get_default()
325
326 def init_checkers(self):
327 pynotify.init("cGmail")
328@@ -139,7 +133,7 @@
329
330 def check(self):
331 self.init_checkers()
332- self.__filter_man.reload_filters()
333+ self.__filter_man.reload_filters(False)
334 mailslists = []
335 for account_id, checker in self.checkers.iteritems():
336 thread.start_new_thread(checker.check, ())
337
338=== modified file 'data/junkfilter_dialog.ui'
339--- data/junkfilter_dialog.ui 2009-08-21 18:13:34 +0000
340+++ data/junkfilter_dialog.ui 2009-09-10 21:59:40 +0000
341@@ -42,6 +42,7 @@
342 <object class="GtkTreeView" id="filters_treeview">
343 <property name="visible">True</property>
344 <property name="can_focus">True</property>
345+ <property name="reorderable">True</property>
346 <property name="rules_hint">True</property>
347 <signal name="button_press_event" handler="on_filters_treeview_button_press"/>
348 <signal name="key_press_event" handler="on_filters_treeview_key_press"/>
349
350=== modified file 'data/junkfilter_popupmenu.ui'
351--- data/junkfilter_popupmenu.ui 2009-08-21 18:13:34 +0000
352+++ data/junkfilter_popupmenu.ui 2009-09-10 21:59:40 +0000
353@@ -5,6 +5,47 @@
354 <object class="GtkMenu" id="menu">
355 <property name="visible">True</property>
356 <child>
357+ <object class="GtkImageMenuItem" id="top">
358+ <property name="label">gtk-goto-top</property>
359+ <property name="visible">True</property>
360+ <property name="use_underline">True</property>
361+ <property name="use_stock">True</property>
362+ <signal name="activate" handler="on_top_activate"/>
363+ </object>
364+ </child>
365+ <child>
366+ <object class="GtkImageMenuItem" id="up">
367+ <property name="label">gtk-go-up</property>
368+ <property name="visible">True</property>
369+ <property name="use_underline">True</property>
370+ <property name="use_stock">True</property>
371+ <signal name="activate" handler="on_up_activate"/>
372+ </object>
373+ </child>
374+ <child>
375+ <object class="GtkImageMenuItem" id="down">
376+ <property name="label">gtk-go-down</property>
377+ <property name="visible">True</property>
378+ <property name="use_underline">True</property>
379+ <property name="use_stock">True</property>
380+ <signal name="activate" handler="on_down_activate"/>
381+ </object>
382+ </child>
383+ <child>
384+ <object class="GtkImageMenuItem" id="bottom">
385+ <property name="label">gtk-goto-bottom</property>
386+ <property name="visible">True</property>
387+ <property name="use_underline">True</property>
388+ <property name="use_stock">True</property>
389+ <signal name="activate" handler="on_bottom_activate"/>
390+ </object>
391+ </child>
392+ <child>
393+ <object class="GtkSeparatorMenuItem" id="menuitem1">
394+ <property name="visible">True</property>
395+ </object>
396+ </child>
397+ <child>
398 <object class="GtkImageMenuItem" id="remove">
399 <property name="label">gtk-remove</property>
400 <property name="visible">True</property>

Subscribers

People subscribed via source and target branches