Merge lp://qastaging/~pulb/cgmail/junkfilter_fixes into lp://qastaging/cgmail
- junkfilter_fixes
- Merge into 0.6-devel
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Francesco Marella | Approve | ||
Review via email:
|
Commit message
Description of the change
To post a comment you must log in.
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> |
great work pat! merging into trunk.