Merge lp:~gtg-user/gtg/multibackends-halfgsoc_merge into lp:~gtg/gtg/old-trunk
- multibackends-halfgsoc_merge
- Merge into old-trunk
Status: | Merged |
---|---|
Merged at revision: | 825 |
Proposed branch: | lp:~gtg-user/gtg/multibackends-halfgsoc_merge |
Merge into: | lp:~gtg/gtg/old-trunk |
Diff against target: |
3899 lines (+2527/-662) 36 files modified
GTG/backends/__init__.py (+170/-11) GTG/backends/backend_localfile.py (+181/-0) GTG/backends/backendsignals.py (+125/-0) GTG/backends/genericbackend.py (+571/-0) GTG/backends/localfile.py (+0/-176) GTG/core/__init__.py (+57/-113) GTG/core/datastore.py (+501/-208) GTG/core/filters_bank.py (+20/-0) GTG/core/requester.py (+51/-18) GTG/core/tagstore.py (+8/-4) GTG/core/task.py (+37/-22) GTG/gtg.py (+34/-34) GTG/gtk/browser/browser.py (+4/-7) GTG/gtk/browser/tagtree.py (+2/-1) GTG/gtk/crashhandler.py (+24/-0) GTG/gtk/delete_dialog.py (+5/-1) GTG/gtk/editor/editor.py (+2/-2) GTG/gtk/manager.py (+10/-6) GTG/gtk/preferences.glade (+2/-55) GTG/gtk/preferences.py (+1/-1) GTG/tests/__init__.py (+10/-1) GTG/tests/test_apidocs.py (+4/-0) GTG/tests/test_backends.py (+191/-0) GTG/tests/test_datastore.py (+360/-0) GTG/tests/test_tagstore.py (+3/-0) GTG/tests/test_taskviewserial.py (+3/-0) GTG/tests/test_tree.py (+5/-0) GTG/tools/borg.py (+33/-0) GTG/tools/keyring.py (+48/-0) GTG/tools/logger.py (+6/-0) GTG/tools/synchronized.py (+14/-0) GTG/tools/taskxml.py (+23/-1) GTG/tools/testingmode.py (+16/-0) Makefile (+1/-1) scripts/debug.sh (+1/-0) scripts/profile_interpret.sh (+4/-0) |
To merge this branch: | bzr merge lp:~gtg-user/gtg/multibackends-halfgsoc_merge |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Lionel Dricot (community) | Approve | ||
Review via email: mp+28258@code.launchpad.net |
This proposal supersedes a proposal from 2010-06-22.
Commit message
Description of the change
It's the last merge request [0] with all the changes we talked about applied.
It seems quite stable, since I had the need to change it rarely.
The other backends will come in separate merges, once this one is accepted.
[0] https:/
Lionel Dricot (ploum-deactivatedaccount) wrote : Posted in a previous version of this proposal | # |
Lionel Dricot (ploum-deactivatedaccount) : | # |
Lionel Dricot (ploum-deactivatedaccount) wrote : | # |
just realized that the unit test for datastore isn't working for me :
Exception RuntimeError: 'maximum recursion depth exceeded while calling a Python object' in <type 'exceptions.
.......
<project/>
Luca Invernizzi (invernizzi) wrote : | # |
On Wed, Jun 23, 2010 at 2:07 PM, Lionel Dricot <email address hidden> wrote:
> just realized that the unit test for datastore isn't working for me :
>
>
> Exception RuntimeError: 'maximum recursion depth exceeded while calling a Python object' in <type 'exceptions.
> .......
> <project/>
>
Fixed!
> --
> https:/
> You proposed lp:~gtg-user/gtg/multibackends-halfgsoc_merge for merging.
>
Preview Diff
1 | === modified file 'GTG/backends/__init__.py' | |||
2 | --- GTG/backends/__init__.py 2010-03-01 01:55:12 +0000 | |||
3 | +++ GTG/backends/__init__.py 2010-06-23 01:19:23 +0000 | |||
4 | @@ -23,14 +23,173 @@ | |||
5 | 23 | (like on the hard disk or on the internet) | 23 | (like on the hard disk or on the internet) |
6 | 24 | and to read projects from this medium | 24 | and to read projects from this medium |
7 | 25 | """ | 25 | """ |
19 | 26 | # | 26 | |
20 | 27 | #Current backends are : | 27 | import sys |
21 | 28 | # | 28 | import uuid |
22 | 29 | # localfile.py : store and read a local XML file | 29 | import os.path |
23 | 30 | # | 30 | |
24 | 31 | # | 31 | from GTG.tools.logger import Log |
25 | 32 | # | 32 | from GTG.tools.borg import Borg |
26 | 33 | # this __init__.py should not be empty. It should list the available backends | 33 | from GTG.backends.genericbackend import GenericBackend |
27 | 34 | # | 34 | from GTG.core import firstrun_tasks |
28 | 35 | #http://www.faqts.com/knowledge_base/view.phtml/aid/4221/fid/538 | 35 | from GTG.tools import cleanxml |
29 | 36 | #http://www.python.org/doc/2.1.3/tut/node8.html | 36 | from GTG.core import CoreConfig |
30 | 37 | |||
31 | 38 | |||
32 | 39 | |||
33 | 40 | class BackendFactory(Borg): | ||
34 | 41 | ''' | ||
35 | 42 | This class holds the information about the backend types. | ||
36 | 43 | Since it's about types, all information is static. The instantiated | ||
37 | 44 | backends are handled in the Datastore. | ||
38 | 45 | It is a Borg for what matters its only state (_backend_modules), | ||
39 | 46 | since it makes no sense of keeping multiple instances of this. | ||
40 | 47 | ''' | ||
41 | 48 | |||
42 | 49 | |||
43 | 50 | BACKEND_PREFIX = "backend_" | ||
44 | 51 | |||
45 | 52 | def __init__(self): | ||
46 | 53 | """ | ||
47 | 54 | Creates a dictionary of the currently available backend modules | ||
48 | 55 | """ | ||
49 | 56 | super(BackendFactory, self).__init__() | ||
50 | 57 | if hasattr(self, "backend_modules"): | ||
51 | 58 | #This object has already been constructed | ||
52 | 59 | return | ||
53 | 60 | self.backend_modules = {} | ||
54 | 61 | #Look for backends in the GTG/backends dir | ||
55 | 62 | this_dir = os.path.dirname(__file__) | ||
56 | 63 | backend_files = filter(lambda f: f.endswith(".py") and \ | ||
57 | 64 | f[ : len(self.BACKEND_PREFIX)] == self.BACKEND_PREFIX , \ | ||
58 | 65 | os.listdir(this_dir)) | ||
59 | 66 | #Create module names | ||
60 | 67 | module_names = map(lambda f: f.replace(".py",""), backend_files) | ||
61 | 68 | Log.debug("Backends found: " + str(module_names)) | ||
62 | 69 | #Load backend modules | ||
63 | 70 | for module_name in module_names: | ||
64 | 71 | extended_module_name = "GTG.backends." + module_name | ||
65 | 72 | try: | ||
66 | 73 | __import__(extended_module_name) | ||
67 | 74 | except ImportError, exception: | ||
68 | 75 | #Something is wrong with this backend, skipping | ||
69 | 76 | Log.debug("Backend %s could not be loaded: %s" % \ | ||
70 | 77 | (module_name, str(exception))) | ||
71 | 78 | continue | ||
72 | 79 | self.backend_modules[module_name] = \ | ||
73 | 80 | sys.modules[extended_module_name] | ||
74 | 81 | |||
75 | 82 | def get_backend(self, backend_name): | ||
76 | 83 | ''' | ||
77 | 84 | Returns the backend module for the backend matching | ||
78 | 85 | backend_name. Else, returns none | ||
79 | 86 | ''' | ||
80 | 87 | if backend_name in self.backend_modules: | ||
81 | 88 | return self.backend_modules[backend_name] | ||
82 | 89 | else: | ||
83 | 90 | Log.debug("Trying to load backend %s, but failed!" % backend_name) | ||
84 | 91 | return None | ||
85 | 92 | |||
86 | 93 | def get_all_backends(self): | ||
87 | 94 | ''' | ||
88 | 95 | Returns a dictionary containing all the backends types | ||
89 | 96 | ''' | ||
90 | 97 | return self.backend_modules | ||
91 | 98 | |||
92 | 99 | def get_new_backend_dict(self, backend_name, additional_parameters = {}): | ||
93 | 100 | ''' | ||
94 | 101 | Constructs a new backend initialization dictionary. In more | ||
95 | 102 | exact terms, creates a dictionary, containing all the necessary | ||
96 | 103 | entries to initialize a backend. | ||
97 | 104 | ''' | ||
98 | 105 | if not self.backend_modules.has_key(backend_name): | ||
99 | 106 | return None | ||
100 | 107 | dic = {} | ||
101 | 108 | module = self.get_backend(backend_name) | ||
102 | 109 | #Different pids are necessary to discern between backends of the same | ||
103 | 110 | # type | ||
104 | 111 | parameters = module.Backend.get_static_parameters() | ||
105 | 112 | #we all the parameters and their default values in dic | ||
106 | 113 | for param_name, param_dic in parameters.iteritems(): | ||
107 | 114 | dic[param_name] = param_dic[GenericBackend.PARAM_DEFAULT_VALUE] | ||
108 | 115 | dic["pid"] = str(uuid.uuid4()) | ||
109 | 116 | dic["module"] = module.Backend.get_name() | ||
110 | 117 | for param_name, param_value in additional_parameters.iteritems(): | ||
111 | 118 | dic[param_name] = param_value | ||
112 | 119 | dic["backend"] = module.Backend(dic) | ||
113 | 120 | return dic | ||
114 | 121 | |||
115 | 122 | def restore_backend_from_xml(self, dic): | ||
116 | 123 | ''' | ||
117 | 124 | Function restoring a backend from its xml description. | ||
118 | 125 | dic should be a dictionary containing at least the key | ||
119 | 126 | - "module", with the module name | ||
120 | 127 | - "xmlobject", with its xml description. | ||
121 | 128 | Every other key is passed as-is to the backend, as parameter. | ||
122 | 129 | |||
123 | 130 | Returns the backend instance, or None is something goes wrong | ||
124 | 131 | ''' | ||
125 | 132 | if not "module" in dic or not "xmlobject" in dic: | ||
126 | 133 | Log.debug ("Malformed backend configuration found! %s" % \ | ||
127 | 134 | dic) | ||
128 | 135 | module = self.get_backend(dic["module"]) | ||
129 | 136 | if module == None: | ||
130 | 137 | Log.debug ("could not load module for backend %s" % \ | ||
131 | 138 | dic["module"]) | ||
132 | 139 | return None | ||
133 | 140 | #we pop the xml object, as it will be redundant when the parameters | ||
134 | 141 | # are set directly in the dict | ||
135 | 142 | xp = dic.pop("xmlobject") | ||
136 | 143 | #Building the dictionary | ||
137 | 144 | parameters_specs = module.Backend.get_static_parameters() | ||
138 | 145 | dic["pid"] = str(xp.getAttribute("pid")) | ||
139 | 146 | for param_name, param_dic in parameters_specs.iteritems(): | ||
140 | 147 | if xp.hasAttribute(param_name): | ||
141 | 148 | #we need to convert the parameter to the right format. | ||
142 | 149 | # we fetch the format from the static_parameters | ||
143 | 150 | param_type = param_dic[GenericBackend.PARAM_TYPE] | ||
144 | 151 | param_value = GenericBackend.cast_param_type_from_string( \ | ||
145 | 152 | xp.getAttribute(param_name), param_type) | ||
146 | 153 | dic[param_name] = param_value | ||
147 | 154 | #We put the backend itself in the dict | ||
148 | 155 | dic["backend"] = module.Backend(dic) | ||
149 | 156 | return dic["backend"] | ||
150 | 157 | |||
151 | 158 | def get_saved_backends_list(self): | ||
152 | 159 | backends_dic = self._read_backend_configuration_file() | ||
153 | 160 | |||
154 | 161 | #Retrocompatibility: default backend has changed name | ||
155 | 162 | for dic in backends_dic: | ||
156 | 163 | if dic["module"] == "localfile": | ||
157 | 164 | dic["module"] = "backend_localfile" | ||
158 | 165 | dic["pid"] = str(uuid.uuid4()) | ||
159 | 166 | dic["need_conversion"] = \ | ||
160 | 167 | dic["xmlobject"].getAttribute("filename") | ||
161 | 168 | |||
162 | 169 | #Now that the backend list is build, we will construct them | ||
163 | 170 | for dic in backends_dic: | ||
164 | 171 | self.restore_backend_from_xml(dic) | ||
165 | 172 | #If no backend available, we create a new using localfile. Xmlobject | ||
166 | 173 | # will be filled in by the backend | ||
167 | 174 | if len(backends_dic) == 0: | ||
168 | 175 | dic = BackendFactory().get_new_backend_dict( \ | ||
169 | 176 | "backend_localfile") | ||
170 | 177 | dic["backend"].this_is_the_first_run(firstrun_tasks.populate()) | ||
171 | 178 | backends_dic.append(dic) | ||
172 | 179 | return backends_dic | ||
173 | 180 | |||
174 | 181 | def _read_backend_configuration_file(self): | ||
175 | 182 | ''' | ||
176 | 183 | Reads the file describing the current backend configuration (project.xml) | ||
177 | 184 | and returns a list of dictionaries, each containing: | ||
178 | 185 | - the xml object defining the backend characteristics under | ||
179 | 186 | "xmlobject" | ||
180 | 187 | - the name of the backend under "module" | ||
181 | 188 | ''' | ||
182 | 189 | # Read configuration file, if it does not exist, create one | ||
183 | 190 | datafile = os.path.join(CoreConfig().get_data_dir(), CoreConfig.DATA_FILE) | ||
184 | 191 | doc, configxml = cleanxml.openxmlfile(datafile, "config") | ||
185 | 192 | xmlproject = doc.getElementsByTagName("backend") | ||
186 | 193 | # collect configured backends | ||
187 | 194 | return [{"xmlobject": xp, \ | ||
188 | 195 | "module": xp.getAttribute("module")} for xp in xmlproject] | ||
189 | 37 | 196 | ||
190 | === added file 'GTG/backends/backend_localfile.py' | |||
191 | --- GTG/backends/backend_localfile.py 1970-01-01 00:00:00 +0000 | |||
192 | +++ GTG/backends/backend_localfile.py 2010-06-23 01:19:23 +0000 | |||
193 | @@ -0,0 +1,181 @@ | |||
194 | 1 | # -*- coding: utf-8 -*- | ||
195 | 2 | # ----------------------------------------------------------------------------- | ||
196 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
197 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
198 | 5 | # | ||
199 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
200 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
201 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
202 | 9 | # version. | ||
203 | 10 | # | ||
204 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
205 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
206 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
207 | 14 | # details. | ||
208 | 15 | # | ||
209 | 16 | # You should have received a copy of the GNU General Public License along with | ||
210 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
211 | 18 | # ----------------------------------------------------------------------------- | ||
212 | 19 | |||
213 | 20 | ''' | ||
214 | 21 | Localfile is a read/write backend that will store your tasks in an XML file | ||
215 | 22 | This file will be in your $XDG_DATA_DIR/gtg folder. | ||
216 | 23 | ''' | ||
217 | 24 | |||
218 | 25 | import os | ||
219 | 26 | import uuid | ||
220 | 27 | |||
221 | 28 | from GTG.backends.genericbackend import GenericBackend | ||
222 | 29 | from GTG.core import CoreConfig | ||
223 | 30 | from GTG.tools import cleanxml, taskxml | ||
224 | 31 | from GTG import _ | ||
225 | 32 | |||
226 | 33 | |||
227 | 34 | |||
228 | 35 | class Backend(GenericBackend): | ||
229 | 36 | |||
230 | 37 | |||
231 | 38 | DEFAULT_PATH = CoreConfig().get_data_dir() #default path for filenames | ||
232 | 39 | |||
233 | 40 | |||
234 | 41 | #Description of the backend (mainly it's data we show the user, only the | ||
235 | 42 | # name is used internally. Please note that BACKEND_NAME and | ||
236 | 43 | # BACKEND_ICON_NAME should *not* be translated. | ||
237 | 44 | _general_description = { \ | ||
238 | 45 | GenericBackend.BACKEND_NAME: "backend_localfile", \ | ||
239 | 46 | GenericBackend.BACKEND_HUMAN_NAME: _("Local File"), \ | ||
240 | 47 | GenericBackend.BACKEND_AUTHORS: ["Lionel Dricot", \ | ||
241 | 48 | "Luca Invernizzi"], \ | ||
242 | 49 | GenericBackend.BACKEND_TYPE: GenericBackend.TYPE_READWRITE, \ | ||
243 | 50 | GenericBackend.BACKEND_DESCRIPTION: \ | ||
244 | 51 | _("Your tasks are saved in a text file (XML format). " + \ | ||
245 | 52 | " This is the most basic and the default way " + \ | ||
246 | 53 | "for GTG to save your tasks."),\ | ||
247 | 54 | } | ||
248 | 55 | |||
249 | 56 | #parameters to configure a new backend of this type. | ||
250 | 57 | #NOTE: should we always give back a different default filename? it can be | ||
251 | 58 | # done, but I'd like to keep this backend simple, so that it can be | ||
252 | 59 | # used as example (invernizzi) | ||
253 | 60 | _static_parameters = { \ | ||
254 | 61 | "path": { \ | ||
255 | 62 | GenericBackend.PARAM_TYPE: GenericBackend.TYPE_STRING, \ | ||
256 | 63 | GenericBackend.PARAM_DEFAULT_VALUE: \ | ||
257 | 64 | os.path.join(DEFAULT_PATH, "gtg_tasks-%s.xml" %(uuid.uuid4())) | ||
258 | 65 | }} | ||
259 | 66 | |||
260 | 67 | def _get_default_filename_path(self, filename = None): | ||
261 | 68 | ''' | ||
262 | 69 | Generates a default path with a random filename | ||
263 | 70 | @param filename: specify a filename | ||
264 | 71 | ''' | ||
265 | 72 | if not filename: | ||
266 | 73 | filename = "gtg_tasks-%s.xml" % (uuid.uuid4()) | ||
267 | 74 | return os.path.join(self.DEFAULT_PATH, filename) | ||
268 | 75 | |||
269 | 76 | def __init__(self, parameters): | ||
270 | 77 | """ | ||
271 | 78 | Instantiates a new backend. | ||
272 | 79 | |||
273 | 80 | @param parameters: should match the dictionary returned in | ||
274 | 81 | get_parameters. Anyway, the backend should care if one expected | ||
275 | 82 | value is None or does not exist in the dictionary. | ||
276 | 83 | @firstrun: only needed for the default backend. It should be | ||
277 | 84 | omitted for all other backends. | ||
278 | 85 | """ | ||
279 | 86 | super(Backend, self).__init__(parameters) | ||
280 | 87 | self.tids = [] | ||
281 | 88 | #####RETROCOMPATIBILIY | ||
282 | 89 | #NOTE: retrocompatibility. We convert "filename" to "path" | ||
283 | 90 | # and we forget about "filename" | ||
284 | 91 | if "need_conversion" in parameters: | ||
285 | 92 | parameters["path"] = os.path.join(self.DEFAULT_PATH, \ | ||
286 | 93 | parameters["need_conversion"]) | ||
287 | 94 | del parameters["need_conversion"] | ||
288 | 95 | if not self.KEY_DEFAULT_BACKEND in parameters: | ||
289 | 96 | parameters[self.KEY_DEFAULT_BACKEND] = True | ||
290 | 97 | #### | ||
291 | 98 | self.doc, self.xmlproj = cleanxml.openxmlfile( \ | ||
292 | 99 | self._parameters["path"], "project") | ||
293 | 100 | |||
294 | 101 | def initialize(self): | ||
295 | 102 | super(Backend, self).initialize() | ||
296 | 103 | self.doc, self.xmlproj = cleanxml.openxmlfile( \ | ||
297 | 104 | self._parameters["path"], "project") | ||
298 | 105 | |||
299 | 106 | def this_is_the_first_run(self, xml): | ||
300 | 107 | #Create the default tasks for the first run. | ||
301 | 108 | #We write the XML object in a file | ||
302 | 109 | self._parameters[self.KEY_DEFAULT_BACKEND] = True | ||
303 | 110 | cleanxml.savexml(self._parameters["path"], xml) | ||
304 | 111 | self.doc, self.xmlproj = cleanxml.openxmlfile(\ | ||
305 | 112 | self._parameters["path"], "project") | ||
306 | 113 | self._parameters[self.KEY_DEFAULT_BACKEND] = True | ||
307 | 114 | |||
308 | 115 | def start_get_tasks(self): | ||
309 | 116 | ''' | ||
310 | 117 | Once this function is launched, the backend can start pushing | ||
311 | 118 | tasks to gtg parameters. | ||
312 | 119 | |||
313 | 120 | @return: start_get_tasks() might not return or finish | ||
314 | 121 | ''' | ||
315 | 122 | tid_list = [] | ||
316 | 123 | for node in self.xmlproj.childNodes: | ||
317 | 124 | tid = node.getAttribute("id") | ||
318 | 125 | if tid not in self.tids: | ||
319 | 126 | self.tids.append(tid) | ||
320 | 127 | task = self.datastore.task_factory(tid) | ||
321 | 128 | if task: | ||
322 | 129 | task = taskxml.task_from_xml(task, node) | ||
323 | 130 | self.datastore.push_task(task) | ||
324 | 131 | |||
325 | 132 | def set_task(self, task): | ||
326 | 133 | tid = task.get_id() | ||
327 | 134 | existing = None | ||
328 | 135 | #First, we find the existing task from the treenode | ||
329 | 136 | for node in self.xmlproj.childNodes: | ||
330 | 137 | if node.getAttribute("id") == tid: | ||
331 | 138 | existing = node | ||
332 | 139 | t_xml = taskxml.task_to_xml(self.doc, task) | ||
333 | 140 | modified = False | ||
334 | 141 | #We then replace the existing node | ||
335 | 142 | if existing and t_xml: | ||
336 | 143 | #We will write only if the task has changed | ||
337 | 144 | if t_xml.toxml() != existing.toxml(): | ||
338 | 145 | self.xmlproj.replaceChild(t_xml, existing) | ||
339 | 146 | modified = True | ||
340 | 147 | #If the node doesn't exist, we create it | ||
341 | 148 | # (it might not be the case in all backends | ||
342 | 149 | else: | ||
343 | 150 | self.xmlproj.appendChild(t_xml) | ||
344 | 151 | modified = True | ||
345 | 152 | #In this particular backend, we write all the tasks | ||
346 | 153 | #This is inherent to the XML file backend | ||
347 | 154 | if modified and self._parameters["path"] and self.doc : | ||
348 | 155 | cleanxml.savexml(self._parameters["path"], self.doc) | ||
349 | 156 | |||
350 | 157 | def remove_task(self, tid): | ||
351 | 158 | ''' Completely remove the task with ID = tid ''' | ||
352 | 159 | for node in self.xmlproj.childNodes: | ||
353 | 160 | if node.getAttribute("id") == tid: | ||
354 | 161 | self.xmlproj.removeChild(node) | ||
355 | 162 | if tid in self.tids: | ||
356 | 163 | self.tids.remove(tid) | ||
357 | 164 | cleanxml.savexml(self._parameters["path"], self.doc) | ||
358 | 165 | |||
359 | 166 | |||
360 | 167 | def quit(self, disable = False): | ||
361 | 168 | ''' | ||
362 | 169 | Called when GTG quits or disconnects the backend. | ||
363 | 170 | ''' | ||
364 | 171 | super(Backend, self).quit(disable) | ||
365 | 172 | |||
366 | 173 | def save_state(self): | ||
367 | 174 | cleanxml.savexml(self._parameters["path"], self.doc, backup=True) | ||
368 | 175 | |||
369 | 176 | def get_number_of_tasks(self): | ||
370 | 177 | ''' | ||
371 | 178 | Returns the number of tasks stored in the backend. Doesn't need to be a | ||
372 | 179 | fast function, is called just for the UI | ||
373 | 180 | ''' | ||
374 | 181 | return len(self.tids) | ||
375 | 0 | 182 | ||
376 | === added file 'GTG/backends/backendsignals.py' | |||
377 | --- GTG/backends/backendsignals.py 1970-01-01 00:00:00 +0000 | |||
378 | +++ GTG/backends/backendsignals.py 2010-06-23 01:19:23 +0000 | |||
379 | @@ -0,0 +1,125 @@ | |||
380 | 1 | # -*- coding: utf-8 -*- | ||
381 | 2 | # ----------------------------------------------------------------------------- | ||
382 | 3 | # Getting Things Gnome! - a personal organizer for the GNOME desktop | ||
383 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
384 | 5 | # | ||
385 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
386 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
387 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
388 | 9 | # version. | ||
389 | 10 | # | ||
390 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
391 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
392 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
393 | 14 | # details. | ||
394 | 15 | # | ||
395 | 16 | # You should have received a copy of the GNU General Public License along with | ||
396 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
397 | 18 | # ----------------------------------------------------------------------------- | ||
398 | 19 | |||
399 | 20 | import gobject | ||
400 | 21 | |||
401 | 22 | from GTG.tools.borg import Borg | ||
402 | 23 | |||
403 | 24 | |||
404 | 25 | |||
405 | 26 | class BackendSignals(Borg): | ||
406 | 27 | ''' | ||
407 | 28 | This class handles the signals that involve backends. | ||
408 | 29 | In particular, it's a wrapper Borg class around a _BackendSignalsGObject | ||
409 | 30 | class, and all method of the wrapped class can be used as if they were part | ||
410 | 31 | of this class | ||
411 | 32 | ''' | ||
412 | 33 | |||
413 | 34 | #error codes to send along with the BACKEND_FAILED signal | ||
414 | 35 | ERRNO_AUTHENTICATION = "authentication failed" | ||
415 | 36 | ERRNO_NETWORK = "network is down" | ||
416 | 37 | ERRNO_DBUS = "Dbus interface cannot be connected" | ||
417 | 38 | |||
418 | 39 | def __init__(self): | ||
419 | 40 | super(BackendSignals, self).__init__() | ||
420 | 41 | if hasattr(self, "_gobject"): | ||
421 | 42 | return | ||
422 | 43 | self._gobject = _BackendSignalsGObject() | ||
423 | 44 | |||
424 | 45 | def __getattr__(self, attr): | ||
425 | 46 | return getattr(self._gobject, attr) | ||
426 | 47 | |||
427 | 48 | |||
428 | 49 | class _BackendSignalsGObject(gobject.GObject): | ||
429 | 50 | |||
430 | 51 | #signal name constants | ||
431 | 52 | BACKEND_STATE_TOGGLED = 'backend-state-toggled' #emitted when a | ||
432 | 53 | #backend is | ||
433 | 54 | #enabled or disabled | ||
434 | 55 | BACKEND_RENAMED = 'backend-renamed' #emitted when a backend is renamed | ||
435 | 56 | BACKEND_ADDED = 'backend-added' | ||
436 | 57 | BACKEND_REMOVED = 'backend-added' #when a backend is deleted | ||
437 | 58 | DEFAULT_BACKEND_LOADED = 'default-backend-loaded' #emitted after all | ||
438 | 59 | # tasks have been | ||
439 | 60 | # loaded from the | ||
440 | 61 | # default backend | ||
441 | 62 | BACKEND_FAILED = 'backend-failed' #something went wrong with a backend | ||
442 | 63 | BACKEND_SYNC_STARTED = 'backend-sync-started' | ||
443 | 64 | BACKEND_SYNC_ENDED = 'backend-sync-ended' | ||
444 | 65 | |||
445 | 66 | __string_signal__ = (gobject.SIGNAL_RUN_FIRST, \ | ||
446 | 67 | gobject.TYPE_NONE, (str, )) | ||
447 | 68 | __none_signal__ = (gobject.SIGNAL_RUN_FIRST, \ | ||
448 | 69 | gobject.TYPE_NONE, ( )) | ||
449 | 70 | __string_string_signal__ = (gobject.SIGNAL_RUN_FIRST, \ | ||
450 | 71 | gobject.TYPE_NONE, (str, str, )) | ||
451 | 72 | |||
452 | 73 | __gsignals__ = {BACKEND_STATE_TOGGLED : __string_signal__, \ | ||
453 | 74 | BACKEND_RENAMED : __string_signal__, \ | ||
454 | 75 | BACKEND_ADDED : __string_signal__, \ | ||
455 | 76 | BACKEND_REMOVED : __string_signal__, \ | ||
456 | 77 | BACKEND_SYNC_STARTED : __string_signal__, \ | ||
457 | 78 | BACKEND_SYNC_ENDED : __string_signal__, \ | ||
458 | 79 | DEFAULT_BACKEND_LOADED: __none_signal__, \ | ||
459 | 80 | BACKEND_FAILED : __string_string_signal__} | ||
460 | 81 | |||
461 | 82 | def __init__(self): | ||
462 | 83 | super(_BackendSignalsGObject, self).__init__() | ||
463 | 84 | self.backends_currently_syncing = [] | ||
464 | 85 | |||
465 | 86 | ############# Signals ######### | ||
466 | 87 | #connecting to signals is fine, but keep an eye if you should emit them. | ||
467 | 88 | #As a general rule, signals should only be emitted in the GenericBackend | ||
468 | 89 | #class | ||
469 | 90 | |||
470 | 91 | def _emit_signal(self, signal, backend_id): | ||
471 | 92 | gobject.idle_add(self.emit, signal, backend_id) | ||
472 | 93 | |||
473 | 94 | def backend_state_changed(self, backend_id): | ||
474 | 95 | self._emit_signal(self.BACKEND_STATE_TOGGLED, backend_id) | ||
475 | 96 | |||
476 | 97 | def backend_renamed(self, backend_id): | ||
477 | 98 | self._emit_signal(self.BACKEND_RENAMED, backend_id) | ||
478 | 99 | |||
479 | 100 | def backend_added(self, backend_id): | ||
480 | 101 | self._emit_signal(self.BACKEND_ADDED, backend_id) | ||
481 | 102 | |||
482 | 103 | def backend_removed(self, backend_id): | ||
483 | 104 | self._emit_signal(self.BACKEND_REMOVED, backend_id) | ||
484 | 105 | |||
485 | 106 | def default_backend_loaded(self): | ||
486 | 107 | gobject.idle_add(self.emit, self.DEFAULT_BACKEND_LOADED) | ||
487 | 108 | |||
488 | 109 | def backend_failed(self, backend_id, error_code): | ||
489 | 110 | gobject.idle_add(self.emit, self.BACKEND_FAILED, backend_id, \ | ||
490 | 111 | error_code) | ||
491 | 112 | |||
492 | 113 | def backend_sync_started(self, backend_id): | ||
493 | 114 | self._emit_signal(self.BACKEND_SYNC_STARTED, backend_id) | ||
494 | 115 | self.backends_currently_syncing.append(backend_id) | ||
495 | 116 | |||
496 | 117 | def backend_sync_ended(self, backend_id): | ||
497 | 118 | self._emit_signal(self.BACKEND_SYNC_ENDED, backend_id) | ||
498 | 119 | try: | ||
499 | 120 | self.backends_currently_syncing.remove(backend_id) | ||
500 | 121 | except: | ||
501 | 122 | pass | ||
502 | 123 | |||
503 | 124 | def is_backend_syncing(self, backend_id): | ||
504 | 125 | return backend_id in self.backends_currently_syncing | ||
505 | 0 | 126 | ||
506 | === added file 'GTG/backends/genericbackend.py' | |||
507 | --- GTG/backends/genericbackend.py 1970-01-01 00:00:00 +0000 | |||
508 | +++ GTG/backends/genericbackend.py 2010-06-23 01:19:23 +0000 | |||
509 | @@ -0,0 +1,571 @@ | |||
510 | 1 | # -*- coding: utf-8 -*- | ||
511 | 2 | # ----------------------------------------------------------------------------- | ||
512 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
513 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
514 | 5 | # | ||
515 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
516 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
517 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
518 | 9 | # version. | ||
519 | 10 | # | ||
520 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
521 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
522 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
523 | 14 | # details. | ||
524 | 15 | # | ||
525 | 16 | # You should have received a copy of the GNU General Public License along with | ||
526 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
527 | 18 | # ----------------------------------------------------------------------------- | ||
528 | 19 | |||
529 | 20 | ''' | ||
530 | 21 | FIXME: document! | ||
531 | 22 | ''' | ||
532 | 23 | |||
533 | 24 | import os | ||
534 | 25 | import sys | ||
535 | 26 | import errno | ||
536 | 27 | import pickle | ||
537 | 28 | import threading | ||
538 | 29 | from collections import deque | ||
539 | 30 | |||
540 | 31 | from GTG.backends.backendsignals import BackendSignals | ||
541 | 32 | from GTG.tools.keyring import Keyring | ||
542 | 33 | from GTG.core import CoreConfig | ||
543 | 34 | from GTG.tools.logger import Log | ||
544 | 35 | |||
545 | 36 | |||
546 | 37 | |||
547 | 38 | |||
548 | 39 | class GenericBackend(object): | ||
549 | 40 | ''' | ||
550 | 41 | Base class for every backend. It's a little more than an interface which | ||
551 | 42 | methods have to be redefined in order for the backend to run. | ||
552 | 43 | ''' | ||
553 | 44 | |||
554 | 45 | |||
555 | 46 | #BACKEND TYPE DESCRIPTION | ||
556 | 47 | #"_general_description" is a dictionary that holds the values for the | ||
557 | 48 | # following keys: | ||
558 | 49 | BACKEND_NAME = "name" #the backend gtg internal name (doesn't change in | ||
559 | 50 | # translations, *must be unique*) | ||
560 | 51 | BACKEND_HUMAN_NAME = "human-friendly-name" #The name shown to the user | ||
561 | 52 | BACKEND_DESCRIPTION = "description" #A short description of the backend | ||
562 | 53 | BACKEND_AUTHORS = "authors" #a list of strings | ||
563 | 54 | BACKEND_TYPE = "type" | ||
564 | 55 | #BACKEND_TYPE is one of: | ||
565 | 56 | TYPE_READWRITE = "readwrite" | ||
566 | 57 | TYPE_READONLY = "readonly" | ||
567 | 58 | TYPE_IMPORT = "import" | ||
568 | 59 | TYPE_EXPORT = "export" | ||
569 | 60 | _general_description = {} | ||
570 | 61 | |||
571 | 62 | |||
572 | 63 | #"static_parameters" is a dictionary of dictionaries, each of which | ||
573 | 64 | #representing a parameter needed to configure the backend. | ||
574 | 65 | #each "sub-dictionary" is identified by this a key representing its name. | ||
575 | 66 | #"static_parameters" will be part of the definition of each | ||
576 | 67 | #particular backend. | ||
577 | 68 | # Each dictionary contains the keys: | ||
578 | 69 | #PARAM_DESCRIPTION = "description" #short description (shown to the user | ||
579 | 70 | # during configuration) | ||
580 | 71 | PARAM_DEFAULT_VALUE = "default_value" # its default value | ||
581 | 72 | PARAM_TYPE = "type" | ||
582 | 73 | #PARAM_TYPE is one of the following (changing this changes the way | ||
583 | 74 | # the user can configure the parameter) | ||
584 | 75 | TYPE_PASSWORD = "password" #the real password is stored in the GNOME | ||
585 | 76 | # keyring | ||
586 | 77 | # This is just a key to find it there | ||
587 | 78 | TYPE_STRING = "string" #generic string, nothing fancy is done | ||
588 | 79 | TYPE_INT = "int" #edit box can contain only integers | ||
589 | 80 | TYPE_BOOL = "bool" #checkbox is shown | ||
590 | 81 | TYPE_LIST_OF_STRINGS = "liststring" #list of strings. the "," character is | ||
591 | 82 | # prohibited in strings | ||
592 | 83 | _static_parameters = {} | ||
593 | 84 | |||
594 | 85 | def initialize(self): | ||
595 | 86 | ''' | ||
596 | 87 | Called each time it is enabled again (including on backend creation). | ||
597 | 88 | Please note that a class instance for each disabled backend *is* | ||
598 | 89 | created, but it's not initialized. | ||
599 | 90 | Optional. | ||
600 | 91 | NOTE: make sure to call super().initialize() | ||
601 | 92 | ''' | ||
602 | 93 | for module_name in self.get_required_modules(): | ||
603 | 94 | sys.modules[module_name]= __import__(module_name) | ||
604 | 95 | self._parameters[self.KEY_ENABLED] = True | ||
605 | 96 | self._is_initialized = True | ||
606 | 97 | #we signal that the backend has been enabled | ||
607 | 98 | self._signal_manager.backend_state_changed(self.get_id()) | ||
608 | 99 | |||
609 | 100 | def start_get_tasks(self): | ||
610 | 101 | ''' | ||
611 | 102 | Once this function is launched, the backend can start pushing | ||
612 | 103 | tasks to gtg parameters. | ||
613 | 104 | |||
614 | 105 | @return: start_get_tasks() might not return or finish | ||
615 | 106 | ''' | ||
616 | 107 | raise NotImplemented() | ||
617 | 108 | |||
618 | 109 | def set_task(self, task): | ||
619 | 110 | ''' | ||
620 | 111 | Save the task in the backend. If the task id is new for the | ||
621 | 112 | backend, then a new task must be created. | ||
622 | 113 | ''' | ||
623 | 114 | pass | ||
624 | 115 | |||
625 | 116 | def remove_task(self, tid): | ||
626 | 117 | ''' Completely remove the task with ID = tid ''' | ||
627 | 118 | pass | ||
628 | 119 | |||
629 | 120 | def has_task(self, tid): | ||
630 | 121 | '''Returns true if the backend has an internal idea | ||
631 | 122 | of the task corresponding to the tid. False otherwise''' | ||
632 | 123 | raise NotImplemented() | ||
633 | 124 | |||
634 | 125 | def new_task_id(self): | ||
635 | 126 | ''' | ||
636 | 127 | Returns an available ID for a new task so that a task with this ID | ||
637 | 128 | can be saved with set_task later. | ||
638 | 129 | ''' | ||
639 | 130 | raise NotImplemented() | ||
640 | 131 | |||
641 | 132 | def this_is_the_first_run(self, xml): | ||
642 | 133 | ''' | ||
643 | 134 | Steps to execute if it's the first time the backend is run. Optional. | ||
644 | 135 | ''' | ||
645 | 136 | pass | ||
646 | 137 | |||
647 | 138 | def purge(self): | ||
648 | 139 | ''' | ||
649 | 140 | Called when a backend will be removed from GTG. Useful for removing | ||
650 | 141 | configuration files. Optional. | ||
651 | 142 | ''' | ||
652 | 143 | pass | ||
653 | 144 | |||
654 | 145 | def get_number_of_tasks(self): | ||
655 | 146 | ''' | ||
656 | 147 | Returns the number of tasks stored in the backend. Doesn't need to be a | ||
657 | 148 | fast function, is called just for the UI | ||
658 | 149 | ''' | ||
659 | 150 | raise NotImplemented() | ||
660 | 151 | |||
661 | 152 | @staticmethod | ||
662 | 153 | def get_required_modules(): | ||
663 | 154 | return [] | ||
664 | 155 | |||
665 | 156 | def quit(self, disable = False): | ||
666 | 157 | ''' | ||
667 | 158 | Called when GTG quits or disconnects the backend. Remember to execute | ||
668 | 159 | also this function when quitting. If disable is True, the backend won't | ||
669 | 160 | be automatically loaded at next GTG start | ||
670 | 161 | ''' | ||
671 | 162 | self._is_initialized = False | ||
672 | 163 | if disable: | ||
673 | 164 | self._parameters[self.KEY_ENABLED] = False | ||
674 | 165 | #we signal that we have been disabled | ||
675 | 166 | self._signal_manager.backend_state_changed(self.get_id()) | ||
676 | 167 | self._signal_manager.backend_sync_ended(self.get_id()) | ||
677 | 168 | syncing_thread = threading.Thread(target = self.sync).run() | ||
678 | 169 | |||
679 | 170 | def save_state(self): | ||
680 | 171 | ''' | ||
681 | 172 | It's the last function executed on a quitting backend, after the | ||
682 | 173 | pending actions have been done. | ||
683 | 174 | Useful to ensure that the state is saved in a consistent manner | ||
684 | 175 | ''' | ||
685 | 176 | pass | ||
686 | 177 | |||
687 | 178 | ############################################################################### | ||
688 | 179 | ###### You don't need to reimplement the functions below this line ############ | ||
689 | 180 | ############################################################################### | ||
690 | 181 | |||
691 | 182 | #These parameters are common to all backends and necessary. | ||
692 | 183 | # They will be added automatically to your _static_parameters list | ||
693 | 184 | #NOTE: for now I'm disabling changing the default backend. Once it's all | ||
694 | 185 | # set up, we will see about that (invernizzi) | ||
695 | 186 | KEY_DEFAULT_BACKEND = "Default" | ||
696 | 187 | KEY_ENABLED = "Enabled" | ||
697 | 188 | KEY_HUMAN_NAME = BACKEND_HUMAN_NAME | ||
698 | 189 | KEY_ATTACHED_TAGS = "attached-tags" | ||
699 | 190 | KEY_USER = "user" | ||
700 | 191 | KEY_PID = "pid" | ||
701 | 192 | ALLTASKS_TAG = "gtg-tags-all" #IXME: moved here to avoid circular imports | ||
702 | 193 | |||
703 | 194 | _static_parameters_obligatory = { \ | ||
704 | 195 | KEY_DEFAULT_BACKEND: { \ | ||
705 | 196 | PARAM_TYPE: TYPE_BOOL, \ | ||
706 | 197 | PARAM_DEFAULT_VALUE: False, \ | ||
707 | 198 | }, \ | ||
708 | 199 | KEY_HUMAN_NAME: { \ | ||
709 | 200 | PARAM_TYPE: TYPE_STRING, \ | ||
710 | 201 | PARAM_DEFAULT_VALUE: "", \ | ||
711 | 202 | }, \ | ||
712 | 203 | KEY_USER: { \ | ||
713 | 204 | PARAM_TYPE: TYPE_STRING, \ | ||
714 | 205 | PARAM_DEFAULT_VALUE: "", \ | ||
715 | 206 | }, \ | ||
716 | 207 | KEY_PID: { \ | ||
717 | 208 | PARAM_TYPE: TYPE_STRING, \ | ||
718 | 209 | PARAM_DEFAULT_VALUE: "", \ | ||
719 | 210 | }, \ | ||
720 | 211 | KEY_ENABLED: { \ | ||
721 | 212 | PARAM_TYPE: TYPE_BOOL, \ | ||
722 | 213 | PARAM_DEFAULT_VALUE: False, \ | ||
723 | 214 | }} | ||
724 | 215 | |||
725 | 216 | _static_parameters_obligatory_for_rw = { \ | ||
726 | 217 | KEY_ATTACHED_TAGS: {\ | ||
727 | 218 | PARAM_TYPE: TYPE_LIST_OF_STRINGS, \ | ||
728 | 219 | PARAM_DEFAULT_VALUE: [ALLTASKS_TAG], \ | ||
729 | 220 | }} | ||
730 | 221 | |||
731 | 222 | #Handy dictionary used in type conversion (from string to type) | ||
732 | 223 | _type_converter = {TYPE_STRING: str, | ||
733 | 224 | TYPE_INT: int, | ||
734 | 225 | } | ||
735 | 226 | |||
736 | 227 | @classmethod | ||
737 | 228 | def _get_static_parameters(cls): | ||
738 | 229 | ''' | ||
739 | 230 | Helper method, used to obtain the full list of the static_parameters | ||
740 | 231 | (user configured and default ones) | ||
741 | 232 | ''' | ||
742 | 233 | if hasattr(cls, "_static_parameters"): | ||
743 | 234 | temp_dic = cls._static_parameters_obligatory.copy() | ||
744 | 235 | if cls._general_description[cls.BACKEND_TYPE] == cls.TYPE_READWRITE: | ||
745 | 236 | for key, value in \ | ||
746 | 237 | cls._static_parameters_obligatory_for_rw.iteritems(): | ||
747 | 238 | temp_dic[key] = value | ||
748 | 239 | for key, value in cls._static_parameters.iteritems(): | ||
749 | 240 | temp_dic[key] = value | ||
750 | 241 | return temp_dic | ||
751 | 242 | else: | ||
752 | 243 | raise NotImplemented("_static_parameters not implemented for " + \ | ||
753 | 244 | "backend %s" % type(cls)) | ||
754 | 245 | |||
755 | 246 | def __init__(self, parameters): | ||
756 | 247 | """ | ||
757 | 248 | Instantiates a new backend. Please note that this is called also for | ||
758 | 249 | disabled backends. Those are not initialized, so you might want to check | ||
759 | 250 | out the initialize() function. | ||
760 | 251 | """ | ||
761 | 252 | if self.KEY_DEFAULT_BACKEND not in parameters: | ||
762 | 253 | parameters[self.KEY_DEFAULT_BACKEND] = True | ||
763 | 254 | if parameters[self.KEY_DEFAULT_BACKEND] or \ | ||
764 | 255 | (not self.KEY_ATTACHED_TAGS in parameters and \ | ||
765 | 256 | self._general_description[self.BACKEND_TYPE] \ | ||
766 | 257 | == self.TYPE_READWRITE): | ||
767 | 258 | parameters[self.KEY_ATTACHED_TAGS] = [self.ALLTASKS_TAG] | ||
768 | 259 | self._parameters = parameters | ||
769 | 260 | self._signal_manager = BackendSignals() | ||
770 | 261 | self._is_initialized = False | ||
771 | 262 | if Log.is_debugging_mode(): | ||
772 | 263 | self.timer_timestep = 5 | ||
773 | 264 | else: | ||
774 | 265 | self.timer_timestep = 1 | ||
775 | 266 | self.to_set_timer = None | ||
776 | 267 | self.please_quit = False | ||
777 | 268 | self.to_set = deque() | ||
778 | 269 | self.to_remove = deque() | ||
779 | 270 | |||
780 | 271 | def get_attached_tags(self): | ||
781 | 272 | ''' | ||
782 | 273 | Returns the list of tags which are handled by this backend | ||
783 | 274 | ''' | ||
784 | 275 | if hasattr(self._parameters, self.KEY_DEFAULT_BACKEND) and \ | ||
785 | 276 | self._parameters[self.KEY_DEFAULT_BACKEND]: | ||
786 | 277 | return [self.ALLTASKS_TAG] | ||
787 | 278 | try: | ||
788 | 279 | return self._parameters[self.KEY_ATTACHED_TAGS] | ||
789 | 280 | except: | ||
790 | 281 | return [] | ||
791 | 282 | |||
792 | 283 | def set_attached_tags(self, tags): | ||
793 | 284 | ''' | ||
794 | 285 | Changes the set of attached tags | ||
795 | 286 | ''' | ||
796 | 287 | self._parameters[self.KEY_ATTACHED_TAGS] = tags | ||
797 | 288 | |||
798 | 289 | @classmethod | ||
799 | 290 | def get_static_parameters(cls): | ||
800 | 291 | """ | ||
801 | 292 | Returns a dictionary of parameters necessary to create a backend. | ||
802 | 293 | """ | ||
803 | 294 | return cls._get_static_parameters() | ||
804 | 295 | |||
805 | 296 | def get_parameters(self): | ||
806 | 297 | """ | ||
807 | 298 | Returns a dictionary of the current parameters. | ||
808 | 299 | """ | ||
809 | 300 | return self._parameters | ||
810 | 301 | |||
811 | 302 | def set_parameter(self, parameter, value): | ||
812 | 303 | self._parameters[parameter] = value | ||
813 | 304 | |||
814 | 305 | @classmethod | ||
815 | 306 | def get_name(cls): | ||
816 | 307 | """ | ||
817 | 308 | Returns the name of the backend as it should be displayed in the UI | ||
818 | 309 | """ | ||
819 | 310 | return cls._get_from_general_description(cls.BACKEND_NAME) | ||
820 | 311 | |||
821 | 312 | @classmethod | ||
822 | 313 | def get_description(cls): | ||
823 | 314 | """Returns a description of the backend""" | ||
824 | 315 | return cls._get_from_general_description(cls.BACKEND_DESCRIPTION) | ||
825 | 316 | |||
826 | 317 | @classmethod | ||
827 | 318 | def get_type(cls): | ||
828 | 319 | """Returns the backend type(readonly, r/w, import, export) """ | ||
829 | 320 | return cls._get_from_general_description(cls.BACKEND_TYPE) | ||
830 | 321 | |||
831 | 322 | @classmethod | ||
832 | 323 | def get_authors(cls): | ||
833 | 324 | ''' | ||
834 | 325 | returns the backend author(s) | ||
835 | 326 | ''' | ||
836 | 327 | return cls._get_from_general_description(cls.BACKEND_AUTHORS) | ||
837 | 328 | |||
838 | 329 | @classmethod | ||
839 | 330 | def _get_from_general_description(cls, key): | ||
840 | 331 | ''' | ||
841 | 332 | Helper method to extract values from cls._general_description. | ||
842 | 333 | Raises an exception if the key is missing (helpful for developers | ||
843 | 334 | adding new backends). | ||
844 | 335 | ''' | ||
845 | 336 | if key in cls._general_description: | ||
846 | 337 | return cls._general_description[key] | ||
847 | 338 | else: | ||
848 | 339 | raise NotImplemented("Key %s is missing from " +\ | ||
849 | 340 | "'self._general_description' of a backend (%s). " + | ||
850 | 341 | "Please add the corresponding value" % (key, type(cls))) | ||
851 | 342 | |||
852 | 343 | @classmethod | ||
853 | 344 | def cast_param_type_from_string(cls, param_value, param_type): | ||
854 | 345 | ''' | ||
855 | 346 | Parameters are saved in a text format, so we have to cast them to the | ||
856 | 347 | appropriate type on loading. This function does exactly that. | ||
857 | 348 | ''' | ||
858 | 349 | #FIXME: we could use pickle (dumps and loads), at least in some cases | ||
859 | 350 | # (invernizzi) | ||
860 | 351 | if param_type in cls._type_converter: | ||
861 | 352 | return cls._type_converter[param_type](param_value) | ||
862 | 353 | elif param_type == cls.TYPE_BOOL: | ||
863 | 354 | if param_value == "True": | ||
864 | 355 | return True | ||
865 | 356 | elif param_value == "False": | ||
866 | 357 | return False | ||
867 | 358 | else: | ||
868 | 359 | raise Exception("Unrecognized bool value '%s'" % | ||
869 | 360 | param_type) | ||
870 | 361 | elif param_type == cls.TYPE_PASSWORD: | ||
871 | 362 | if param_value == -1: | ||
872 | 363 | return None | ||
873 | 364 | return Keyring().get_password(int(param_value)) | ||
874 | 365 | elif param_type == cls.TYPE_LIST_OF_STRINGS: | ||
875 | 366 | the_list = param_value.split(",") | ||
876 | 367 | if not isinstance(the_list, list): | ||
877 | 368 | the_list = [the_list] | ||
878 | 369 | return the_list | ||
879 | 370 | else: | ||
880 | 371 | raise NotImplemented("I don't know what type is '%s'" % | ||
881 | 372 | param_type) | ||
882 | 373 | |||
883 | 374 | def cast_param_type_to_string(self, param_type, param_value): | ||
884 | 375 | ''' | ||
885 | 376 | Inverse of cast_param_type_from_string | ||
886 | 377 | ''' | ||
887 | 378 | if param_type == GenericBackend.TYPE_PASSWORD: | ||
888 | 379 | if param_value == None: | ||
889 | 380 | return str(-1) | ||
890 | 381 | else: | ||
891 | 382 | return str(Keyring().set_password( | ||
892 | 383 | "GTG stored password -" + self.get_id(), param_value)) | ||
893 | 384 | elif param_type == GenericBackend.TYPE_LIST_OF_STRINGS: | ||
894 | 385 | if param_value == []: | ||
895 | 386 | return "" | ||
896 | 387 | return reduce(lambda a, b: a + "," + b, param_value) | ||
897 | 388 | else: | ||
898 | 389 | return str(param_value) | ||
899 | 390 | |||
900 | 391 | def get_id(self): | ||
901 | 392 | ''' | ||
902 | 393 | returns the backends id, used in the datastore for indexing backends | ||
903 | 394 | ''' | ||
904 | 395 | return self.get_name() + "@" + self._parameters["pid"] | ||
905 | 396 | |||
906 | 397 | @classmethod | ||
907 | 398 | def get_human_default_name(cls): | ||
908 | 399 | ''' | ||
909 | 400 | returns the user friendly default backend name. | ||
910 | 401 | ''' | ||
911 | 402 | return cls._general_description[cls.BACKEND_HUMAN_NAME] | ||
912 | 403 | |||
913 | 404 | def get_human_name(self): | ||
914 | 405 | ''' | ||
915 | 406 | returns the user customized backend name. If the user hasn't | ||
916 | 407 | customized it, returns the default one | ||
917 | 408 | ''' | ||
918 | 409 | if self.KEY_HUMAN_NAME in self._parameters and \ | ||
919 | 410 | self._parameters[self.KEY_HUMAN_NAME] != "": | ||
920 | 411 | return self._parameters[self.KEY_HUMAN_NAME] | ||
921 | 412 | else: | ||
922 | 413 | return self.get_human_default_name() | ||
923 | 414 | |||
924 | 415 | def set_human_name(self, name): | ||
925 | 416 | ''' | ||
926 | 417 | sets a custom name for the backend | ||
927 | 418 | ''' | ||
928 | 419 | self._parameters[self.KEY_HUMAN_NAME] = name | ||
929 | 420 | #we signal the change | ||
930 | 421 | self._signal_manager.backend_renamed(self.get_id()) | ||
931 | 422 | |||
932 | 423 | def is_enabled(self): | ||
933 | 424 | ''' | ||
934 | 425 | Returns if the backend is enabled | ||
935 | 426 | ''' | ||
936 | 427 | return self.get_parameters()[GenericBackend.KEY_ENABLED] or \ | ||
937 | 428 | self.is_default() | ||
938 | 429 | |||
939 | 430 | def is_default(self): | ||
940 | 431 | ''' | ||
941 | 432 | Returns if the backend is enabled | ||
942 | 433 | ''' | ||
943 | 434 | return self.get_parameters()[GenericBackend.KEY_DEFAULT_BACKEND] | ||
944 | 435 | |||
945 | 436 | def is_initialized(self): | ||
946 | 437 | ''' | ||
947 | 438 | Returns if the backend is up and running | ||
948 | 439 | ''' | ||
949 | 440 | return self._is_initialized | ||
950 | 441 | |||
951 | 442 | def get_parameter_type(self, param_name): | ||
952 | 443 | try: | ||
953 | 444 | return self.get_static_parameters()[param_name][self.PARAM_TYPE] | ||
954 | 445 | except KeyError: | ||
955 | 446 | return None | ||
956 | 447 | |||
957 | 448 | def register_datastore(self, datastore): | ||
958 | 449 | self.datastore = datastore | ||
959 | 450 | |||
960 | 451 | ############################################################################### | ||
961 | 452 | ### HELPER FUNCTIONS ########################################################## | ||
962 | 453 | ############################################################################### | ||
963 | 454 | |||
964 | 455 | def _store_pickled_file(self, path, data): | ||
965 | 456 | ''' | ||
966 | 457 | A helper function to save some object in a file. | ||
967 | 458 | @param path: a relative path. A good choice is | ||
968 | 459 | "backend_name/object_name" | ||
969 | 460 | @param data: the object | ||
970 | 461 | ''' | ||
971 | 462 | path = os.path.join(CoreConfig().get_data_dir(), path) | ||
972 | 463 | #mkdir -p | ||
973 | 464 | try: | ||
974 | 465 | os.makedirs(os.path.dirname(path)) | ||
975 | 466 | except OSError, exception: | ||
976 | 467 | if exception.errno != errno.EEXIST: | ||
977 | 468 | raise | ||
978 | 469 | #saving | ||
979 | 470 | #try: | ||
980 | 471 | with open(path, 'wb') as file: | ||
981 | 472 | pickle.dump(data, file) | ||
982 | 473 | #except pickle.PickleError: | ||
983 | 474 | #pass | ||
984 | 475 | |||
985 | 476 | def _load_pickled_file(self, path, default_value = None): | ||
986 | 477 | ''' | ||
987 | 478 | A helper function to load some object from a file. | ||
988 | 479 | @param path: the relative path of the file | ||
989 | 480 | @param default_value: the value to return if the file is missing or | ||
990 | 481 | corrupt | ||
991 | 482 | @returns object: the needed object, or default_value | ||
992 | 483 | ''' | ||
993 | 484 | path = os.path.join(CoreConfig().get_data_dir(), path) | ||
994 | 485 | if not os.path.exists(path): | ||
995 | 486 | return default_value | ||
996 | 487 | else: | ||
997 | 488 | try: | ||
998 | 489 | with open(path, 'r') as file: | ||
999 | 490 | return pickle.load(file) | ||
1000 | 491 | except pickle.PickleError: | ||
1001 | 492 | return default_value | ||
1002 | 493 | |||
1003 | 494 | ############################################################################### | ||
1004 | 495 | ### THREADING ################################################################# | ||
1005 | 496 | ############################################################################### | ||
1006 | 497 | |||
1007 | 498 | def __try_launch_setting_thread(self): | ||
1008 | 499 | ''' | ||
1009 | 500 | Helper function to launch the setting thread, if it's not running. | ||
1010 | 501 | ''' | ||
1011 | 502 | if self.to_set_timer == None and self.is_enabled(): | ||
1012 | 503 | self.to_set_timer = threading.Timer(self.timer_timestep, \ | ||
1013 | 504 | self.launch_setting_thread) | ||
1014 | 505 | self.to_set_timer.start() | ||
1015 | 506 | |||
1016 | 507 | def launch_setting_thread(self): | ||
1017 | 508 | ''' | ||
1018 | 509 | This function is launched as a separate thread. Its job is to perform | ||
1019 | 510 | the changes that have been issued from GTG core. In particular, for | ||
1020 | 511 | each task in the self.to_set queue, a task has to be modified or to be | ||
1021 | 512 | created (if the tid is new), and for each task in the self.to_remove | ||
1022 | 513 | queue, a task has to be deleted | ||
1023 | 514 | ''' | ||
1024 | 515 | while not self.please_quit: | ||
1025 | 516 | try: | ||
1026 | 517 | task = self.to_set.pop() | ||
1027 | 518 | except IndexError: | ||
1028 | 519 | break | ||
1029 | 520 | #time.sleep(4) | ||
1030 | 521 | tid = task.get_id() | ||
1031 | 522 | if tid not in self.to_remove: | ||
1032 | 523 | self.set_task(task) | ||
1033 | 524 | |||
1034 | 525 | while not self.please_quit: | ||
1035 | 526 | try: | ||
1036 | 527 | tid = self.to_remove.pop() | ||
1037 | 528 | except IndexError: | ||
1038 | 529 | break | ||
1039 | 530 | self.remove_task(tid) | ||
1040 | 531 | #we release the weak lock | ||
1041 | 532 | self.to_set_timer = None | ||
1042 | 533 | |||
1043 | 534 | def queue_set_task(self, task): | ||
1044 | 535 | ''' Save the task in the backend. ''' | ||
1045 | 536 | tid = task.get_id() | ||
1046 | 537 | if task not in self.to_set and tid not in self.to_remove: | ||
1047 | 538 | self.to_set.appendleft(task) | ||
1048 | 539 | self.__try_launch_setting_thread() | ||
1049 | 540 | |||
1050 | 541 | def queue_remove_task(self, tid): | ||
1051 | 542 | ''' | ||
1052 | 543 | Queues task to be removed. | ||
1053 | 544 | @param tid: The Task ID of the task to be removed | ||
1054 | 545 | ''' | ||
1055 | 546 | if tid not in self.to_remove: | ||
1056 | 547 | self.to_remove.appendleft(tid) | ||
1057 | 548 | self.__try_launch_setting_thread() | ||
1058 | 549 | return None | ||
1059 | 550 | |||
1060 | 551 | def sync(self): | ||
1061 | 552 | ''' | ||
1062 | 553 | Helper method. Forces the backend to perform all the pending changes. | ||
1063 | 554 | It is usually called upon quitting the backend. | ||
1064 | 555 | ''' | ||
1065 | 556 | #FIXME: this function should become part of the r/w r/o generic class | ||
1066 | 557 | # for backends | ||
1067 | 558 | if self.to_set_timer != None: | ||
1068 | 559 | self.please_quit = True | ||
1069 | 560 | try: | ||
1070 | 561 | self.to_set_timer.cancel() | ||
1071 | 562 | except: | ||
1072 | 563 | pass | ||
1073 | 564 | try: | ||
1074 | 565 | self.to_set_timer.join(5) | ||
1075 | 566 | except: | ||
1076 | 567 | pass | ||
1077 | 568 | self.please_quit = False | ||
1078 | 569 | self.launch_setting_thread() | ||
1079 | 570 | self.save_state() | ||
1080 | 571 | |||
1081 | 0 | 572 | ||
1082 | === removed file 'GTG/backends/localfile.py' | |||
1083 | --- GTG/backends/localfile.py 2010-04-26 23:12:57 +0000 | |||
1084 | +++ GTG/backends/localfile.py 1970-01-01 00:00:00 +0000 | |||
1085 | @@ -1,176 +0,0 @@ | |||
1086 | 1 | # -*- coding: utf-8 -*- | ||
1087 | 2 | # ----------------------------------------------------------------------------- | ||
1088 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
1089 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
1090 | 5 | # | ||
1091 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
1092 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
1093 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
1094 | 9 | # version. | ||
1095 | 10 | # | ||
1096 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
1097 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
1098 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
1099 | 14 | # details. | ||
1100 | 15 | # | ||
1101 | 16 | # You should have received a copy of the GNU General Public License along with | ||
1102 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
1103 | 18 | # ----------------------------------------------------------------------------- | ||
1104 | 19 | |||
1105 | 20 | ''' | ||
1106 | 21 | Localfile is a read/write backend that will store your tasks in an XML file | ||
1107 | 22 | This file will be in your $XDG_DATA_DIR/gtg folder. | ||
1108 | 23 | ''' | ||
1109 | 24 | |||
1110 | 25 | import os | ||
1111 | 26 | import uuid | ||
1112 | 27 | |||
1113 | 28 | from GTG.core import CoreConfig | ||
1114 | 29 | from GTG.tools import cleanxml, taskxml | ||
1115 | 30 | |||
1116 | 31 | def get_name(): | ||
1117 | 32 | """Returns the name of the backend as it should be displayed in the UI""" | ||
1118 | 33 | return "Local File" | ||
1119 | 34 | |||
1120 | 35 | def get_description(): | ||
1121 | 36 | """Returns a description of the backend""" | ||
1122 | 37 | return "Your tasks are saved in an XML file located in your HOME folder" | ||
1123 | 38 | |||
1124 | 39 | def get_parameters(): | ||
1125 | 40 | """ | ||
1126 | 41 | Returns a dictionary of parameters. Keys should be strings and | ||
1127 | 42 | are the name of the parameter. | ||
1128 | 43 | Values are string with value : string, password, int, bool | ||
1129 | 44 | and are an information about the type of the parameter | ||
1130 | 45 | Currently, only string is supported. | ||
1131 | 46 | """ | ||
1132 | 47 | dic = {} | ||
1133 | 48 | dic["filename"] = "string" | ||
1134 | 49 | return dic | ||
1135 | 50 | |||
1136 | 51 | def get_features(): | ||
1137 | 52 | """Returns a dict of features supported by this backend""" | ||
1138 | 53 | return {} | ||
1139 | 54 | |||
1140 | 55 | def get_type(): | ||
1141 | 56 | """Type is one of : readwrite, readonly, import, export""" | ||
1142 | 57 | return "readwrite" | ||
1143 | 58 | |||
1144 | 59 | class Backend: | ||
1145 | 60 | def __init__(self, parameters, firstrunxml=None): | ||
1146 | 61 | """ | ||
1147 | 62 | Instantiates a new backend. | ||
1148 | 63 | |||
1149 | 64 | @param parameters: should match the dictionary returned in | ||
1150 | 65 | get_parameters. Anyway, the backend should care if one expected value is | ||
1151 | 66 | None or does not exist in the dictionary. | ||
1152 | 67 | @firstrun: only needed for the default backend. It should be omitted for | ||
1153 | 68 | all other backends. | ||
1154 | 69 | """ | ||
1155 | 70 | self.tids = [] | ||
1156 | 71 | self.pid = 1 | ||
1157 | 72 | if "filename" in parameters: | ||
1158 | 73 | zefile = parameters["filename"] | ||
1159 | 74 | #If zefile is None, we create a new file | ||
1160 | 75 | else: | ||
1161 | 76 | zefile = "%s.xml" %(uuid.uuid4()) | ||
1162 | 77 | parameters["filename"] = zefile | ||
1163 | 78 | #For the day we want to open files somewhere else | ||
1164 | 79 | default_folder = True | ||
1165 | 80 | if default_folder: | ||
1166 | 81 | self.zefile = os.path.join(CoreConfig.DATA_DIR, zefile) | ||
1167 | 82 | self.filename = zefile | ||
1168 | 83 | else: | ||
1169 | 84 | self.zefile = zefile | ||
1170 | 85 | self.filename = zefile | ||
1171 | 86 | #Create the default tasks for the first run. | ||
1172 | 87 | #We write the XML object in a file | ||
1173 | 88 | if firstrunxml and not os.path.exists(zefile): | ||
1174 | 89 | #shutil.copy(firstrunfile,self.zefile) | ||
1175 | 90 | cleanxml.savexml(self.zefile, firstrunxml) | ||
1176 | 91 | self.doc, self.xmlproj = cleanxml.openxmlfile(self.zefile, "project") | ||
1177 | 92 | |||
1178 | 93 | def start_get_tasks(self,push_task_func,task_factory_func): | ||
1179 | 94 | ''' | ||
1180 | 95 | Once this function is launched, the backend can start pushing | ||
1181 | 96 | tasks to gtg parameters. | ||
1182 | 97 | |||
1183 | 98 | @push_task_func: a function that takes a Task as parameter | ||
1184 | 99 | and pushes it into GTG. | ||
1185 | 100 | @task_factory_func: a function that takes a tid as parameter | ||
1186 | 101 | and returns a Task object with the given pid. | ||
1187 | 102 | |||
1188 | 103 | @return: start_get_tasks() might not return or finish | ||
1189 | 104 | ''' | ||
1190 | 105 | tid_list = [] | ||
1191 | 106 | for node in self.xmlproj.childNodes: | ||
1192 | 107 | #time.sleep(2) | ||
1193 | 108 | tid = node.getAttribute("id") | ||
1194 | 109 | if tid not in self.tids: | ||
1195 | 110 | self.tids.append(tid) | ||
1196 | 111 | task = task_factory_func(tid) | ||
1197 | 112 | task = taskxml.task_from_xml(task,node) | ||
1198 | 113 | push_task_func(task) | ||
1199 | 114 | #print "#### finishing pushing tasks" | ||
1200 | 115 | |||
1201 | 116 | def set_task(self, task): | ||
1202 | 117 | ''' Save the task in the backend ''' | ||
1203 | 118 | #time.sleep(4) | ||
1204 | 119 | tid = task.get_id() | ||
1205 | 120 | if tid not in self.tids: | ||
1206 | 121 | self.tids.append(tid) | ||
1207 | 122 | existing = None | ||
1208 | 123 | #First, we find the existing task from the treenode | ||
1209 | 124 | for node in self.xmlproj.childNodes: | ||
1210 | 125 | if node.getAttribute("id") == tid: | ||
1211 | 126 | existing = node | ||
1212 | 127 | t_xml = taskxml.task_to_xml(self.doc, task) | ||
1213 | 128 | modified = False | ||
1214 | 129 | #We then replace the existing node | ||
1215 | 130 | if existing and t_xml: | ||
1216 | 131 | #We will write only if the task has changed | ||
1217 | 132 | if t_xml.toxml() != existing.toxml(): | ||
1218 | 133 | self.xmlproj.replaceChild(t_xml, existing) | ||
1219 | 134 | modified = True | ||
1220 | 135 | #If the node doesn't exist, we create it | ||
1221 | 136 | # (it might not be the case in all backends | ||
1222 | 137 | else: | ||
1223 | 138 | self.xmlproj.appendChild(t_xml) | ||
1224 | 139 | modified = True | ||
1225 | 140 | #In this particular backend, we write all the tasks | ||
1226 | 141 | #This is inherent to the XML file backend | ||
1227 | 142 | if modified and self.zefile and self.doc : | ||
1228 | 143 | cleanxml.savexml(self.zefile, self.doc) | ||
1229 | 144 | return None | ||
1230 | 145 | |||
1231 | 146 | def remove_task(self, tid): | ||
1232 | 147 | ''' Completely remove the task with ID = tid ''' | ||
1233 | 148 | for node in self.xmlproj.childNodes: | ||
1234 | 149 | if node.getAttribute("id") == tid: | ||
1235 | 150 | self.xmlproj.removeChild(node) | ||
1236 | 151 | if tid in self.tids: | ||
1237 | 152 | self.tids.remove(tid) | ||
1238 | 153 | cleanxml.savexml(self.zefile, self.doc) | ||
1239 | 154 | |||
1240 | 155 | def new_task_id(self): | ||
1241 | 156 | ''' | ||
1242 | 157 | Returns an available ID for a new task so that a task with this ID | ||
1243 | 158 | can be saved with set_task later. | ||
1244 | 159 | If None, then GTG will create a new ID by itself. | ||
1245 | 160 | The ID cannot contain the character "@". | ||
1246 | 161 | ''' | ||
1247 | 162 | k = 0 | ||
1248 | 163 | pid = self.pid | ||
1249 | 164 | newid = "%s@%s" %(k, pid) | ||
1250 | 165 | while str(newid) in self.tids: | ||
1251 | 166 | k += 1 | ||
1252 | 167 | newid = "%s@%s" %(k, pid) | ||
1253 | 168 | self.tids.append(newid) | ||
1254 | 169 | return newid | ||
1255 | 170 | |||
1256 | 171 | def quit(self): | ||
1257 | 172 | ''' | ||
1258 | 173 | Called when GTG quits or disconnects the backend. | ||
1259 | 174 | (Subclasses might pass here) | ||
1260 | 175 | ''' | ||
1261 | 176 | cleanxml.savexml(self.zefile, self.doc, backup=True) | ||
1262 | 177 | 0 | ||
1263 | === modified file 'GTG/core/__init__.py' | |||
1264 | --- GTG/core/__init__.py 2010-05-26 09:54:42 +0000 | |||
1265 | +++ GTG/core/__init__.py 2010-06-23 01:19:23 +0000 | |||
1266 | @@ -41,136 +41,80 @@ | |||
1267 | 41 | #=== IMPORT ==================================================================== | 41 | #=== IMPORT ==================================================================== |
1268 | 42 | import os | 42 | import os |
1269 | 43 | from xdg.BaseDirectory import xdg_data_home, xdg_config_home | 43 | from xdg.BaseDirectory import xdg_data_home, xdg_config_home |
1270 | 44 | from GTG.tools import cleanxml | ||
1271 | 45 | from configobj import ConfigObj | 44 | from configobj import ConfigObj |
1276 | 46 | 45 | from GTG.tools.testingmode import TestingMode | |
1277 | 47 | from GTG.core import firstrun_tasks | 46 | |
1278 | 48 | 47 | import GTG | |
1279 | 49 | class CoreConfig: | 48 | from GTG.tools.logger import Log |
1280 | 49 | from GTG.tools.borg import Borg | ||
1281 | 50 | |||
1282 | 51 | |||
1283 | 52 | |||
1284 | 53 | class CoreConfig(Borg): | ||
1285 | 50 | 54 | ||
1286 | 55 | |||
1287 | 51 | #The projects and tasks are of course DATA ! | 56 | #The projects and tasks are of course DATA ! |
1288 | 52 | #We then use XDG_DATA for them | 57 | #We then use XDG_DATA for them |
1289 | 53 | #Don't forget the "/" at the end. | 58 | #Don't forget the "/" at the end. |
1290 | 54 | DATA_DIR = os.path.join(xdg_data_home,'gtg/') | ||
1291 | 55 | DATA_FILE = "projects.xml" | 59 | DATA_FILE = "projects.xml" |
1292 | 56 | CONF_DIR = os.path.join(xdg_config_home,'gtg/') | ||
1293 | 57 | CONF_FILE = "gtg.conf" | 60 | CONF_FILE = "gtg.conf" |
1294 | 58 | TASK_CONF_FILE = "tasks.conf" | 61 | TASK_CONF_FILE = "tasks.conf" |
1295 | 59 | conf_dict = None | 62 | conf_dict = None |
1296 | 60 | #DBUS | 63 | #DBUS |
1297 | 61 | BUSNAME = "org.GTG" | 64 | BUSNAME = "org.GTG" |
1298 | 62 | BUSINTERFACE = "/org/GTG" | 65 | BUSINTERFACE = "/org/GTG" |
1299 | 66 | #TAGS | ||
1300 | 67 | ALLTASKS_TAG = "gtg-tags-all" | ||
1301 | 63 | 68 | ||
1302 | 64 | def __init__(self): | 69 | def __init__(self): |
1315 | 65 | if not os.path.exists(self.CONF_DIR): | 70 | if hasattr(self, 'data_dir'): |
1316 | 66 | os.makedirs(self.CONF_DIR) | 71 | #Borg has already been initialized |
1317 | 67 | if not os.path.exists(self.DATA_DIR): | 72 | return |
1318 | 68 | os.makedirs(self.DATA_DIR) | 73 | if TestingMode().get_testing_mode(): |
1319 | 69 | if not os.path.exists(self.CONF_DIR + self.CONF_FILE): | 74 | #we avoid running tests in the user data dir |
1320 | 70 | f = open(self.CONF_DIR + self.CONF_FILE, "w") | 75 | self.data_dir = '/tmp/GTG_TESTS/data' |
1321 | 71 | f.close() | 76 | self.conf_dir = '/tmp/GTG_TESTS/conf' |
1322 | 72 | if not os.path.exists(self.CONF_DIR + self.TASK_CONF_FILE): | 77 | else: |
1323 | 73 | f = open(self.CONF_DIR + self.TASK_CONF_FILE, "w") | 78 | self.data_dir = os.path.join(xdg_data_home,'gtg/') |
1324 | 74 | f.close() | 79 | self.conf_dir = os.path.join(xdg_config_home,'gtg/') |
1325 | 75 | for file in [self.CONF_DIR + self.CONF_FILE, | 80 | if not os.path.exists(self.conf_dir): |
1326 | 76 | self.CONF_DIR + self.TASK_CONF_FILE]: | 81 | os.makedirs(self.conf_dir) |
1327 | 82 | if not os.path.exists(self.data_dir): | ||
1328 | 83 | os.makedirs(self.data_dir) | ||
1329 | 84 | if not os.path.exists(self.conf_dir + self.CONF_FILE): | ||
1330 | 85 | f = open(self.conf_dir + self.CONF_FILE, "w") | ||
1331 | 86 | f.close() | ||
1332 | 87 | if not os.path.exists(self.conf_dir + self.TASK_CONF_FILE): | ||
1333 | 88 | f = open(self.conf_dir + self.TASK_CONF_FILE, "w") | ||
1334 | 89 | f.close() | ||
1335 | 90 | for file in [self.conf_dir + self.CONF_FILE, | ||
1336 | 91 | self.conf_dir + self.TASK_CONF_FILE]: | ||
1337 | 77 | if not ((file, os.R_OK) and os.access(file, os.W_OK)): | 92 | if not ((file, os.R_OK) and os.access(file, os.W_OK)): |
1338 | 78 | raise Exception("File " + file + \ | 93 | raise Exception("File " + file + \ |
1340 | 79 | " is a configuration file for gtg, but it " + \ | 94 | " is a configuration file for gtg, but it " |
1341 | 80 | "cannot be read or written. Please check it") | 95 | "cannot be read or written. Please check it") |
1344 | 81 | self.conf_dict = ConfigObj(self.CONF_DIR + self.CONF_FILE) | 96 | self.conf_dict = ConfigObj(self.conf_dir + self.CONF_FILE) |
1345 | 82 | self.task_conf_dict = ConfigObj(self.CONF_DIR + self.TASK_CONF_FILE) | 97 | self.task_conf_dict = ConfigObj(self.conf_dir + self.TASK_CONF_FILE) |
1346 | 83 | 98 | ||
1348 | 84 | def save_config(self): | 99 | def save(self): |
1349 | 100 | ''' Saves the configuration of CoreConfig ''' | ||
1350 | 85 | self.conf_dict.write() | 101 | self.conf_dict.write() |
1351 | 86 | self.task_conf_dict.write() | 102 | self.task_conf_dict.write() |
1442 | 87 | 103 | ||
1443 | 88 | def get_backends_list(self): | 104 | def get_icons_directories(self): |
1444 | 89 | backend_fn = [] | 105 | ''' |
1445 | 90 | 106 | Returns the directories containing the icons | |
1446 | 91 | # Check if config dir exists, if not create it | 107 | ''' |
1447 | 92 | if not os.path.exists(self.DATA_DIR): | 108 | return [GTG.DATA_DIR, os.path.join(GTG.DATA_DIR, "icons")] |
1448 | 93 | os.makedirs(self.DATA_DIR) | 109 | |
1449 | 94 | 110 | def get_data_dir(self): | |
1450 | 95 | # Read configuration file, if it does not exist, create one | 111 | return self.data_dir |
1451 | 96 | datafile = self.DATA_DIR + self.DATA_FILE | 112 | |
1452 | 97 | doc, configxml = cleanxml.openxmlfile(datafile,"config") #pylint: disable-msg=W0612 | 113 | def set_data_dir(self, path): |
1453 | 98 | xmlproject = doc.getElementsByTagName("backend") | 114 | self.data_dir = path |
1454 | 99 | # collect configred backends | 115 | |
1455 | 100 | pid = 1 | 116 | def get_conf_dir(self): |
1456 | 101 | for xp in xmlproject: | 117 | return self.conf_dir |
1457 | 102 | dic = {} | 118 | |
1458 | 103 | #We have some retrocompatibility code | 119 | def set_conf_dir(self, path): |
1459 | 104 | #A backend without the module attribute is pre-rev.105 | 120 | self.conf_dir = path |
1370 | 105 | #and is considered as "filename" | ||
1371 | 106 | if xp.hasAttribute("module"): | ||
1372 | 107 | dic["module"] = str(xp.getAttribute("module")) | ||
1373 | 108 | dic["pid"] = str(xp.getAttribute("pid")) | ||
1374 | 109 | #The following "else" could be removed later | ||
1375 | 110 | else: | ||
1376 | 111 | dic["module"] = "localfile" | ||
1377 | 112 | dic["pid"] = str(pid) | ||
1378 | 113 | |||
1379 | 114 | dic["xmlobject"] = xp | ||
1380 | 115 | pid += 1 | ||
1381 | 116 | backend_fn.append(dic) | ||
1382 | 117 | |||
1383 | 118 | firstrun = False | ||
1384 | 119 | #If no backend available, we create a new using localfile | ||
1385 | 120 | if len(backend_fn) == 0: | ||
1386 | 121 | dic = {} | ||
1387 | 122 | dic["module"] = "localfile" | ||
1388 | 123 | dic["pid"] = "1" | ||
1389 | 124 | backend_fn.append(dic) | ||
1390 | 125 | firstrun = True | ||
1391 | 126 | |||
1392 | 127 | #Now that the backend list is build, we will construct them | ||
1393 | 128 | #Remember that b is a dictionnary | ||
1394 | 129 | for b in backend_fn: | ||
1395 | 130 | #We dynamically import modules needed | ||
1396 | 131 | module_name = "GTG.backends.%s"%b["module"] | ||
1397 | 132 | #FIXME : we should throw an error if the backend is not importable | ||
1398 | 133 | module = __import__(module_name) | ||
1399 | 134 | module = getattr(module, "backends") | ||
1400 | 135 | classobj = getattr(module, b["module"]) | ||
1401 | 136 | b["parameters"] = classobj.get_parameters() | ||
1402 | 137 | #If creating the default backend, we don't have the xmlobject yet | ||
1403 | 138 | if "xmlobject" in b: | ||
1404 | 139 | xp = b.pop("xmlobject") | ||
1405 | 140 | #We will try to get the parameters | ||
1406 | 141 | for key in b["parameters"]: | ||
1407 | 142 | if xp.hasAttribute(key): | ||
1408 | 143 | b[key] = str(xp.getAttribute(key)) | ||
1409 | 144 | if firstrun: | ||
1410 | 145 | frx = firstrun_tasks.populate() | ||
1411 | 146 | back = classobj.Backend(b,firstrunxml=frx) | ||
1412 | 147 | else: | ||
1413 | 148 | back = classobj.Backend(b) | ||
1414 | 149 | #We put the backend itself in the dic | ||
1415 | 150 | b["backend"] = back | ||
1416 | 151 | |||
1417 | 152 | return backend_fn | ||
1418 | 153 | |||
1419 | 154 | |||
1420 | 155 | #If initial save, we don't close stuffs. | ||
1421 | 156 | def save_datastore(self,ds,initial_save=False): | ||
1422 | 157 | doc,xmlconfig = cleanxml.emptydoc("config") | ||
1423 | 158 | for b in ds.get_all_backends(): | ||
1424 | 159 | param = b.get_parameters() | ||
1425 | 160 | t_xml = doc.createElement("backend") | ||
1426 | 161 | for key in param: | ||
1427 | 162 | #We dont want parameters,backend,xmlobject | ||
1428 | 163 | if key not in ["backend","parameters","xmlobject"]: | ||
1429 | 164 | t_xml.setAttribute(str(key),str(param[key])) | ||
1430 | 165 | #Saving all the projects at close | ||
1431 | 166 | xmlconfig.appendChild(t_xml) | ||
1432 | 167 | if not initial_save: | ||
1433 | 168 | b.quit() | ||
1434 | 169 | |||
1435 | 170 | datafile = self.DATA_DIR + self.DATA_FILE | ||
1436 | 171 | cleanxml.savexml(datafile,doc,backup=True) | ||
1437 | 172 | |||
1438 | 173 | #Saving the tagstore | ||
1439 | 174 | if not initial_save: | ||
1440 | 175 | ts = ds.get_tagstore() | ||
1441 | 176 | ts.save() | ||
1460 | 177 | 121 | ||
1461 | === modified file 'GTG/core/datastore.py' | |||
1462 | --- GTG/core/datastore.py 2010-05-26 08:55:45 +0000 | |||
1463 | +++ GTG/core/datastore.py 2010-06-23 01:19:23 +0000 | |||
1464 | @@ -18,267 +18,560 @@ | |||
1465 | 18 | # ----------------------------------------------------------------------------- | 18 | # ----------------------------------------------------------------------------- |
1466 | 19 | 19 | ||
1467 | 20 | """ | 20 | """ |
1469 | 21 | datastore contains a list of TagSource objects, which are proxies between a backend and the datastore itself | 21 | The DaataStore contains a list of TagSource objects, which are proxies |
1470 | 22 | between a backend and the datastore itself | ||
1471 | 22 | """ | 23 | """ |
1472 | 23 | 24 | ||
1473 | 24 | import threading | 25 | import threading |
1491 | 25 | import gobject | 26 | import uuid |
1492 | 26 | import time | 27 | import os.path |
1493 | 27 | 28 | from collections import deque | |
1494 | 28 | from GTG.core import tagstore, requester | 29 | |
1495 | 29 | from GTG.core.task import Task | 30 | from GTG.core import tagstore, requester |
1496 | 30 | from GTG.core.tree import Tree | 31 | from GTG.core.task import Task |
1497 | 31 | 32 | from GTG.core.tree import Tree | |
1498 | 32 | 33 | from GTG.core import CoreConfig | |
1499 | 33 | #Only the datastore should access to the backend | 34 | from GTG.tools.logger import Log |
1500 | 34 | DEFAULT_BACKEND = "1" | 35 | from GTG.backends.genericbackend import GenericBackend |
1501 | 35 | #If you want to debug a backend, it can be useful to disable the threads | 36 | from GTG.tools import cleanxml |
1502 | 36 | #Currently, it's python threads (and not idle_add, which is not useful) | 37 | from GTG.tools.keyring import Keyring |
1503 | 37 | THREADING = True | 38 | from GTG.backends.backendsignals import BackendSignals |
1504 | 38 | 39 | from GTG.tools.synchronized import synchronized | |
1505 | 39 | 40 | from GTG.tools.borg import Borg | |
1506 | 40 | class DataStore: | 41 | |
1507 | 41 | """ A wrapper around a backend that provides an API for adding/removing tasks """ | 42 | |
1508 | 43 | class DataStore(object): | ||
1509 | 44 | ''' | ||
1510 | 45 | A wrapper around all backends that is responsible for keeping the backend | ||
1511 | 46 | instances. It can enable, disable, register and destroy backends, and acts | ||
1512 | 47 | as interface between the backends and GTG core. | ||
1513 | 48 | You should not interface yourself directly with the DataStore: use the | ||
1514 | 49 | Requester instead (which also sends signals as you issue commands). | ||
1515 | 50 | ''' | ||
1516 | 51 | |||
1517 | 52 | |||
1518 | 42 | def __init__(self): | 53 | def __init__(self): |
1521 | 43 | """ Initializes a DataStore object """ | 54 | ''' |
1522 | 44 | self.backends = {} | 55 | Initializes a DataStore object |
1523 | 56 | ''' | ||
1524 | 57 | self.backends = {} #dictionary {backend_name_string: Backend instance} | ||
1525 | 45 | self.open_tasks = Tree() | 58 | self.open_tasks = Tree() |
1526 | 46 | # self.closed_tasks = Tree() | ||
1527 | 47 | self.requester = requester.Requester(self) | 59 | self.requester = requester.Requester(self) |
1528 | 48 | self.tagstore = tagstore.TagStore(self.requester) | 60 | self.tagstore = tagstore.TagStore(self.requester) |
1532 | 49 | 61 | self._backend_signals = BackendSignals() | |
1533 | 50 | def all_tasks(self): | 62 | self.mutex = threading.RLock() |
1534 | 51 | """ | 63 | self.is_default_backend_loaded = False |
1535 | 64 | self._backend_signals.connect('default-backend-loaded', \ | ||
1536 | 65 | self._activate_non_default_backends) | ||
1537 | 66 | self.filtered_datastore = FilteredDataStore(self) | ||
1538 | 67 | |||
1539 | 68 | ########################################################################## | ||
1540 | 69 | ### Helper functions (get_ methods for Datastore embedded objects) | ||
1541 | 70 | ########################################################################## | ||
1542 | 71 | |||
1543 | 72 | def get_tagstore(self): | ||
1544 | 73 | ''' | ||
1545 | 74 | Helper function to obtain the Tagstore associated with this DataStore | ||
1546 | 75 | @return GTG.core.tagstore.TagStore: the tagstore object | ||
1547 | 76 | ''' | ||
1548 | 77 | return self.tagstore | ||
1549 | 78 | |||
1550 | 79 | def get_requester(self): | ||
1551 | 80 | ''' | ||
1552 | 81 | Helper function to get the Requester associate with this DataStore | ||
1553 | 82 | @returns GTG.core.requester.Requester: the requester associated with | ||
1554 | 83 | this datastore | ||
1555 | 84 | ''' | ||
1556 | 85 | return self.requester | ||
1557 | 86 | |||
1558 | 87 | def get_tasks_tree(self): | ||
1559 | 88 | ''' | ||
1560 | 89 | Helper function to get a Tree with all the tasks contained in this | ||
1561 | 90 | Datastore | ||
1562 | 91 | @returns GTG.core.tree.Tree: a task tree (the main one) | ||
1563 | 92 | ''' | ||
1564 | 93 | return self.open_tasks | ||
1565 | 94 | |||
1566 | 95 | ########################################################################## | ||
1567 | 96 | ### Tasks functions | ||
1568 | 97 | ########################################################################## | ||
1569 | 98 | |||
1570 | 99 | def get_all_tasks(self): | ||
1571 | 100 | ''' | ||
1572 | 52 | Returns list of all keys of open tasks | 101 | Returns list of all keys of open tasks |
1574 | 53 | """ | 102 | @return a list of strings: a list of task ids |
1575 | 103 | ''' | ||
1576 | 54 | return self.open_tasks.get_all_keys() | 104 | return self.open_tasks.get_all_keys() |
1577 | 55 | 105 | ||
1578 | 56 | def has_task(self, tid): | 106 | def has_task(self, tid): |
1580 | 57 | """ | 107 | ''' |
1581 | 58 | Returns true if the tid is among the open or closed tasks for | 108 | Returns true if the tid is among the open or closed tasks for |
1582 | 59 | this DataStore, False otherwise. | 109 | this DataStore, False otherwise. |
1586 | 60 | param tid: Task ID to search for | 110 | @param tid: Task ID to search for |
1587 | 61 | """ | 111 | @return bool: True if the task is present |
1588 | 62 | return self.open_tasks.has_node(tid) #or self.closed_tasks.has_node(tid) | 112 | ''' |
1589 | 113 | return self.open_tasks.has_node(tid) | ||
1590 | 63 | 114 | ||
1591 | 64 | def get_task(self, tid): | 115 | def get_task(self, tid): |
1593 | 65 | """ | 116 | ''' |
1594 | 66 | Returns the internal task object for the given tid, or None if the | 117 | Returns the internal task object for the given tid, or None if the |
1595 | 67 | tid is not present in this DataStore. | 118 | tid is not present in this DataStore. |
1596 | 68 | @param tid: Task ID to retrieve | 119 | @param tid: Task ID to retrieve |
1605 | 69 | """ | 120 | @returns GTG.core.task.Task or None: whether the Task is present |
1606 | 70 | if tid: | 121 | or not |
1607 | 71 | if self.has_task(tid): | 122 | ''' |
1608 | 72 | task = self.__internal_get_task(tid) | 123 | if self.has_task(tid): |
1609 | 73 | else: | 124 | return self.open_tasks.get_node(tid) |
1602 | 74 | #print "no task %s" %tid | ||
1603 | 75 | task = None | ||
1604 | 76 | return task | ||
1610 | 77 | else: | 125 | else: |
1612 | 78 | print "get_task should take a tid" | 126 | Log.debug("requested non-existent task") |
1613 | 79 | return None | 127 | return None |
1614 | 80 | 128 | ||
1647 | 81 | def __internal_get_task(self, tid): | 129 | def task_factory(self, tid, newtask = False): |
1648 | 82 | return self.open_tasks.get_node(tid) | 130 | ''' |
1649 | 83 | # if toreturn == None: | 131 | Instantiates the given task id as a Task object. |
1650 | 84 | # self.closed_tasks.get_node(tid) | 132 | @param tid: a task id. Must be unique |
1651 | 85 | #else: | 133 | @param newtask: True if the task has never been seen before |
1652 | 86 | #print "error : this task doesn't exist in either tree" | 134 | @return Task: a Task instance |
1653 | 87 | #pass | 135 | ''' |
1654 | 88 | #we return None if the task doesn't exist | 136 | return Task(tid, self.requester, newtask) |
1655 | 89 | # return toreturn | 137 | |
1656 | 90 | 138 | def new_task(self): | |
1625 | 91 | def delete_task(self, tid): | ||
1626 | 92 | """ | ||
1627 | 93 | Deletes the given task entirely from this DataStore, and unlinks | ||
1628 | 94 | it from the task's parent. | ||
1629 | 95 | @return: True if task was deleted, or False if the tid was not | ||
1630 | 96 | present in this DataStore. | ||
1631 | 97 | """ | ||
1632 | 98 | if not tid or not self.has_task(tid): | ||
1633 | 99 | return False | ||
1634 | 100 | |||
1635 | 101 | self.__internal_get_task(tid).delete() | ||
1636 | 102 | uid, pid = tid.split('@') #pylint: disable-msg=W0612 | ||
1637 | 103 | back = self.backends[pid] | ||
1638 | 104 | #Check that the task still exist. It might have been deleted | ||
1639 | 105 | #by its parent a few line earlier : | ||
1640 | 106 | if self.has_task(tid): | ||
1641 | 107 | self.open_tasks.remove_node(tid) | ||
1642 | 108 | # self.closed_tasks.remove_node(tid) | ||
1643 | 109 | back.remove_task(tid) | ||
1644 | 110 | return True | ||
1645 | 111 | |||
1646 | 112 | def new_task(self,pid=None): | ||
1657 | 113 | """ | 139 | """ |
1658 | 114 | Creates a blank new task in this DataStore. | 140 | Creates a blank new task in this DataStore. |
1661 | 115 | @param pid: (Optional) parent ID that this task should be a child of. | 141 | New task is created in all the backends that collect all tasks (among |
1662 | 116 | If not specified, the task will be a child of the default backend. | 142 | them, the default backend). The default backend uses the same task id |
1663 | 143 | in its own internal representation. | ||
1664 | 117 | @return: The task object that was created. | 144 | @return: The task object that was created. |
1665 | 118 | """ | 145 | """ |
1673 | 119 | if not pid: | 146 | task = self.task_factory(uuid.uuid4(), True) |
1667 | 120 | pid = DEFAULT_BACKEND | ||
1668 | 121 | newtid = self.backends[pid].new_task_id() | ||
1669 | 122 | while self.has_task(newtid): | ||
1670 | 123 | print "error : tid already exists" | ||
1671 | 124 | newtid = self.backends[pid].new_task_id() | ||
1672 | 125 | task = Task(newtid, self.requester,newtask=True) | ||
1674 | 126 | self.open_tasks.add_node(task) | 147 | self.open_tasks.add_node(task) |
1675 | 127 | task.set_sync_func(self.backends[pid].set_task,callsync=False) | ||
1676 | 128 | return task | 148 | return task |
1696 | 129 | 149 | ||
1697 | 130 | def get_tagstore(self): | 150 | @synchronized |
1698 | 131 | return self.tagstore | 151 | def push_task(self, task, backend_capabilities = 'bypass for now'): |
1699 | 132 | 152 | ''' | |
1700 | 133 | def get_requester(self): | 153 | Adds the given task object to the task tree. In other words, registers |
1701 | 134 | return self.requester | 154 | the given task in the GTG task set. |
1702 | 135 | 155 | @param task: A valid task object (a GTG.core.task.Task) | |
1703 | 136 | def get_tasks_tree(self): | 156 | @return bool: True if the task has been accepted |
1704 | 137 | """ return: Open tasks tree """ | 157 | ''' |
1705 | 138 | return self.open_tasks | 158 | |
1706 | 139 | 159 | if self.has_task(task.get_id()): | |
1707 | 140 | def push_task(self,task): | 160 | return False |
1689 | 141 | """ | ||
1690 | 142 | Adds the given task object as a node to the open tasks tree. | ||
1691 | 143 | @param task: A valid task object | ||
1692 | 144 | """ | ||
1693 | 145 | tid = task.get_id() | ||
1694 | 146 | if self.has_task(tid): | ||
1695 | 147 | print "pushing an existing task. We should care about modifications" | ||
1708 | 148 | else: | 161 | else: |
1709 | 149 | uid, pid = tid.split('@') | ||
1710 | 150 | self.open_tasks.add_node(task) | 162 | self.open_tasks.add_node(task) |
1711 | 151 | task.set_loaded() | 163 | task.set_loaded() |
1723 | 152 | task.set_sync_func(self.backends[pid].set_task,callsync=False) | 164 | if self.is_default_backend_loaded: |
1724 | 153 | 165 | task.sync() | |
1725 | 154 | def task_factory(self,tid): | 166 | return True |
1726 | 155 | """ | 167 | |
1727 | 156 | Instantiates the given task id as a Task object. | 168 | ########################################################################## |
1728 | 157 | @param tid: The id of the task to instantiate | 169 | ### Backends functions |
1729 | 158 | @return: The task object instantiated for tid | 170 | ########################################################################## |
1730 | 159 | """ | 171 | |
1731 | 160 | task = None | 172 | def get_all_backends(self, disabled = False): |
1732 | 161 | if self.has_task(tid): | 173 | """ |
1733 | 162 | print "error : tid already exists" | 174 | returns list of all registered backends for this DataStore. |
1734 | 175 | @param disabled: If disabled is True, attaches also the list of disabled backends | ||
1735 | 176 | @return list: a list of TaskSource objects | ||
1736 | 177 | """ | ||
1737 | 178 | #NOTE: consider cashing this result for speed. | ||
1738 | 179 | result = [] | ||
1739 | 180 | for backend in self.backends.itervalues(): | ||
1740 | 181 | if backend.is_enabled() or disabled: | ||
1741 | 182 | result.append(backend) | ||
1742 | 183 | return result | ||
1743 | 184 | |||
1744 | 185 | def get_backend(self, backend_id): | ||
1745 | 186 | ''' | ||
1746 | 187 | Returns a backend given its id | ||
1747 | 188 | @param backend_id: a backend id | ||
1748 | 189 | @returns GTG.core.datastore.TaskSource or None: the requested backend, | ||
1749 | 190 | or none | ||
1750 | 191 | ''' | ||
1751 | 192 | if backend_id in self.backends: | ||
1752 | 193 | return self.backends[backend_id] | ||
1753 | 163 | else: | 194 | else: |
1757 | 164 | task = Task(tid, self.requester, newtask=False) | 195 | return None |
1755 | 165 | return task | ||
1756 | 166 | |||
1758 | 167 | 196 | ||
1760 | 168 | def register_backend(self, dic): | 197 | def register_backend(self, backend_dic): |
1761 | 169 | """ | 198 | """ |
1762 | 170 | Registers a TaskSource as a backend for this DataStore | 199 | Registers a TaskSource as a backend for this DataStore |
1766 | 171 | @param dic: Dictionary object with a "backend" and "pid" | 200 | @param backend_dic: Dictionary object containing all the |
1767 | 172 | specified. dic["pid"] should be the parent ID to use | 201 | parameters to initialize the backend (filename...). It should |
1768 | 173 | with the backend specified in dic["backend"]. | 202 | also contain the backend class (under "backend"), and its unique |
1769 | 203 | id (under "pid") | ||
1770 | 174 | """ | 204 | """ |
1780 | 175 | if "backend" in dic: | 205 | if "backend" in backend_dic: |
1781 | 176 | pid = dic["pid"] | 206 | if "pid" not in backend_dic: |
1782 | 177 | backend = dic["backend"] | 207 | Log.debug("registering a backend without pid.") |
1783 | 178 | source = TaskSource(backend, dic) | 208 | return None |
1784 | 179 | self.backends[pid] = source | 209 | backend = backend_dic["backend"] |
1785 | 180 | #Filling the backend | 210 | #Checking that is a new backend |
1786 | 181 | #Doing this at start is more efficient than | 211 | if backend.get_id() in self.backends: |
1787 | 182 | #after the GUI is launched | 212 | Log.debug("registering already registered backend") |
1788 | 183 | source.start_get_tasks(self.push_task,self.task_factory) | 213 | return None |
1789 | 214 | source = TaskSource(requester = self.requester, | ||
1790 | 215 | backend = backend, | ||
1791 | 216 | datastore = self.filtered_datastore) | ||
1792 | 217 | self.backends[backend.get_id()] = source | ||
1793 | 218 | #we notify that a new backend is present | ||
1794 | 219 | self._backend_signals.backend_added(backend.get_id()) | ||
1795 | 220 | #saving the backend in the correct dictionary (backends for enabled | ||
1796 | 221 | # backends, disabled_backends for the disabled ones) | ||
1797 | 222 | #this is useful for retro-compatibility | ||
1798 | 223 | if not GenericBackend.KEY_ENABLED in backend_dic: | ||
1799 | 224 | source.set_parameter(GenericBackend.KEY_ENABLED, True) | ||
1800 | 225 | if not GenericBackend.KEY_DEFAULT_BACKEND in backend_dic: | ||
1801 | 226 | source.set_parameter(GenericBackend.KEY_DEFAULT_BACKEND, True) | ||
1802 | 227 | #if it's enabled, we initialize it | ||
1803 | 228 | if source.is_enabled() and \ | ||
1804 | 229 | (self.is_default_backend_loaded or source.is_default()): | ||
1805 | 230 | source.initialize(connect_signals = False) | ||
1806 | 231 | #Filling the backend | ||
1807 | 232 | #Doing this at start is more efficient than | ||
1808 | 233 | #after the GUI is launched | ||
1809 | 234 | source.start_get_tasks() | ||
1810 | 235 | return source | ||
1811 | 184 | else: | 236 | else: |
1824 | 185 | print "Register a dic without backend key: BUG" | 237 | Log.debug("Tried to register a backend without a pid") |
1825 | 186 | 238 | ||
1826 | 187 | def unregister_backend(self, backend): | 239 | def _activate_non_default_backends(self, sender = None): |
1827 | 188 | """ Unimplemented """ | 240 | ''' |
1828 | 189 | print "unregister backend %s not implemented" %backend | 241 | Non-default backends have to wait until the default loads before |
1829 | 190 | 242 | being activated. This function is called after the first default | |
1830 | 191 | def get_all_backends(self): | 243 | backend has loaded all its tasks. |
1831 | 192 | """ returns list of all registered backends for this DataStore """ | 244 | ''' |
1832 | 193 | l = [] | 245 | if self.is_default_backend_loaded: |
1833 | 194 | for key in self.backends: | 246 | Log.debug("spurious call") |
1834 | 195 | l.append(self.backends[key]) | 247 | return |
1835 | 196 | return l | 248 | self.is_default_backend_loaded = True |
1836 | 249 | for backend in self.backends.itervalues(): | ||
1837 | 250 | if backend.is_enabled() and not backend.is_default(): | ||
1838 | 251 | backend.initialize() | ||
1839 | 252 | backend.start_get_tasks() | ||
1840 | 253 | self.flush_all_tasks(backend.get_id()) | ||
1841 | 254 | |||
1842 | 255 | def set_backend_enabled(self, backend_id, state): | ||
1843 | 256 | """ | ||
1844 | 257 | The backend corresponding to backend_id is enabled or disabled | ||
1845 | 258 | according to "state". | ||
1846 | 259 | Disable: | ||
1847 | 260 | Quits a backend and disables it (which means it won't be | ||
1848 | 261 | automatically loaded next time GTG is started) | ||
1849 | 262 | Enable: | ||
1850 | 263 | Reloads a disabled backend. Backend must be already known by the | ||
1851 | 264 | Datastore | ||
1852 | 265 | @parma backend_id: a backend id | ||
1853 | 266 | @param state: True to enable, False to disable | ||
1854 | 267 | """ | ||
1855 | 268 | if backend_id in self.backends: | ||
1856 | 269 | backend = self.backends[backend_id] | ||
1857 | 270 | current_state = backend.is_enabled() | ||
1858 | 271 | if current_state == True and state == False: | ||
1859 | 272 | #we disable the backend | ||
1860 | 273 | backend.quit(disable = True) | ||
1861 | 274 | elif current_state == False and state == True: | ||
1862 | 275 | if self.is_default_backend_loaded == True: | ||
1863 | 276 | backend.initialize() | ||
1864 | 277 | self.flush_all_tasks(backend_id) | ||
1865 | 278 | else: | ||
1866 | 279 | #will be activated afterwards | ||
1867 | 280 | backend.set_parameter(GenericBackend.KEY_ENABLED, | ||
1868 | 281 | True) | ||
1869 | 282 | |||
1870 | 283 | def remove_backend(self, backend_id): | ||
1871 | 284 | ''' | ||
1872 | 285 | Removes a backend, and forgets it ever existed. | ||
1873 | 286 | @param backend_id: a backend id | ||
1874 | 287 | ''' | ||
1875 | 288 | if backend_id in self.backends: | ||
1876 | 289 | backend = self.backends[backend_id] | ||
1877 | 290 | if backend.is_enabled(): | ||
1878 | 291 | self.set_backend_enabled(backend_id, False) | ||
1879 | 292 | backend.purge() | ||
1880 | 293 | #we notify that the backend has been deleted | ||
1881 | 294 | self._backend_signals.backend_removed(backend.get_id()) | ||
1882 | 295 | del self.backends[backend_id] | ||
1883 | 296 | |||
1884 | 297 | def backend_change_attached_tags(self, backend_id, tag_names): | ||
1885 | 298 | ''' | ||
1886 | 299 | Changes the tags for which a backend should store a task | ||
1887 | 300 | @param backend_id: a backend_id | ||
1888 | 301 | @param tag_names: the new set of tags. This should not be a tag object, | ||
1889 | 302 | just the tag name. | ||
1890 | 303 | ''' | ||
1891 | 304 | backend = self.backends[backend_id] | ||
1892 | 305 | backend.set_attached_tags(tag_names) | ||
1893 | 306 | |||
1894 | 307 | def flush_all_tasks(self, backend_id): | ||
1895 | 308 | ''' | ||
1896 | 309 | This function will cause all tasks to be checked against the backend | ||
1897 | 310 | identified with backend_id. If tasks need to be added or removed, it | ||
1898 | 311 | will be done here. | ||
1899 | 312 | It has to be run after the creation of a new backend (or an alteration | ||
1900 | 313 | of its "attached tags"), so that the tasks which are already loaded in | ||
1901 | 314 | the Tree will be saved in the proper backends | ||
1902 | 315 | @param backend_id: a backend id | ||
1903 | 316 | ''' | ||
1904 | 317 | def _internal_flush_all_tasks(): | ||
1905 | 318 | backend = self.backends[backend_id] | ||
1906 | 319 | for task_id in self.requester.get_all_tasks_list(): | ||
1907 | 320 | backend.queue_set_task(None, task_id) | ||
1908 | 321 | t = threading.Thread(target = _internal_flush_all_tasks).start() | ||
1909 | 322 | self.backends[backend_id].start_get_tasks() | ||
1910 | 323 | |||
1911 | 324 | def save(self, quit = False): | ||
1912 | 325 | ''' | ||
1913 | 326 | Saves the backends parameters. | ||
1914 | 327 | @param quit: If quit is true, backends are shut down | ||
1915 | 328 | ''' | ||
1916 | 329 | doc,xmlconfig = cleanxml.emptydoc("config") | ||
1917 | 330 | #we ask all the backends to quit first. | ||
1918 | 331 | if quit: | ||
1919 | 332 | for b in self.get_all_backends(): | ||
1920 | 333 | #NOTE:we could do this in parallel. Maybe a quit and | ||
1921 | 334 | #has_quit would be faster (invernizzi) | ||
1922 | 335 | b.quit() | ||
1923 | 336 | #we save the parameters | ||
1924 | 337 | for b in self.get_all_backends(disabled = True): | ||
1925 | 338 | t_xml = doc.createElement("backend") | ||
1926 | 339 | for key, value in b.get_parameters().iteritems(): | ||
1927 | 340 | if key in ["backend", "xmlobject"]: | ||
1928 | 341 | #We don't want parameters,backend,xmlobject | ||
1929 | 342 | continue | ||
1930 | 343 | param_type = b.get_parameter_type(key) | ||
1931 | 344 | value = b.cast_param_type_to_string(param_type, value) | ||
1932 | 345 | t_xml.setAttribute(str(key), value) | ||
1933 | 346 | #Saving all the projects at close | ||
1934 | 347 | xmlconfig.appendChild(t_xml) | ||
1935 | 348 | |||
1936 | 349 | datafile = os.path.join(CoreConfig().get_data_dir(), CoreConfig.DATA_FILE) | ||
1937 | 350 | cleanxml.savexml(datafile,doc,backup=True) | ||
1938 | 351 | |||
1939 | 352 | #Saving the tagstore | ||
1940 | 353 | ts = self.get_tagstore() | ||
1941 | 354 | ts.save() | ||
1942 | 355 | |||
1943 | 356 | def request_task_deletion(self, tid): | ||
1944 | 357 | ''' | ||
1945 | 358 | This is a proxy function to request a task deletion from a backend | ||
1946 | 359 | @param tid: the tid of the task to remove | ||
1947 | 360 | ''' | ||
1948 | 361 | self.requester.delete_task(tid) | ||
1949 | 362 | |||
1950 | 197 | 363 | ||
1951 | 198 | class TaskSource(): | 364 | class TaskSource(): |
1954 | 199 | """ transparent interface between the real backend and the datastore """ | 365 | ''' |
1955 | 200 | def __init__(self, backend, parameters): | 366 | Transparent interface between the real backend and the DataStore. |
1956 | 367 | Is in charge of connecting and disconnecting to signals | ||
1957 | 368 | ''' | ||
1958 | 369 | def __init__(self, requester, backend, datastore): | ||
1959 | 201 | """ | 370 | """ |
1960 | 202 | Instantiates a TaskSource object. | 371 | Instantiates a TaskSource object. |
1963 | 203 | @param backend: (Required) Task Backend being wrapperized | 372 | @param requester: a Requester |
1964 | 204 | @param parameters: Dictionary of custom parameters. | 373 | @param backend: the backend being wrapped |
1965 | 374 | @param datastore: a FilteredDatastore | ||
1966 | 205 | """ | 375 | """ |
1967 | 206 | self.backend = backend | 376 | self.backend = backend |
1973 | 207 | self.dic = parameters | 377 | self.req = requester |
1974 | 208 | self.to_set = [] | 378 | self.backend.register_datastore(datastore) |
1975 | 209 | self.to_remove = [] | 379 | self.to_set = deque() |
1976 | 210 | self.lock = threading.Lock() | 380 | self.to_remove = deque() |
1977 | 211 | self.count_set = 0 | 381 | self.task_filter = self.get_task_filter_for_backend() |
1978 | 382 | if Log.is_debugging_mode(): | ||
1979 | 383 | self.timer_timestep = 5 | ||
1980 | 384 | else: | ||
1981 | 385 | self.timer_timestep = 1 | ||
1982 | 386 | self.set_task_handle = None | ||
1983 | 387 | self.remove_task_handle = None | ||
1984 | 388 | self.to_set_timer = None | ||
1985 | 212 | 389 | ||
1988 | 213 | def start_get_tasks(self,push_task,task_factory): | 390 | def start_get_tasks(self): |
1989 | 214 | """ | 391 | '''' |
1990 | 215 | Maps the TaskSource to the backend and starts threading. | 392 | Maps the TaskSource to the backend and starts threading. |
1998 | 216 | This must be called before the DataStore is usable. | 393 | ''' |
1999 | 217 | """ | 394 | threading.Thread(target = self.__start_get_tasks).start() |
2000 | 218 | func = self.backend.start_get_tasks | 395 | |
2001 | 219 | t = threading.Thread(target=func,args=(push_task,task_factory)) | 396 | def __start_get_tasks(self): |
2002 | 220 | t.start() | 397 | ''' |
2003 | 221 | 398 | Loads all task from the backend and connects its signals afterwards. | |
2004 | 222 | def set_task(self, task): | 399 | Launched as a thread by start_get_tasks |
2005 | 400 | ''' | ||
2006 | 401 | self.backend.start_get_tasks() | ||
2007 | 402 | self._connect_signals() | ||
2008 | 403 | if self.backend.is_default(): | ||
2009 | 404 | BackendSignals().default_backend_loaded() | ||
2010 | 405 | |||
2011 | 406 | def get_task_filter_for_backend(self): | ||
2012 | 407 | ''' | ||
2013 | 408 | Fiter that checks if the task should be stored in this backend. | ||
2014 | 409 | |||
2015 | 410 | @returns function: a function that accepts a task and returns True/False | ||
2016 | 411 | whether the task should be stored or not | ||
2017 | 412 | ''' | ||
2018 | 413 | raw_filter = self.req.get_filter("backend_filter").get_function() | ||
2019 | 414 | return lambda task: raw_filter(task, \ | ||
2020 | 415 | set(self.backend.get_attached_tags())) | ||
2021 | 416 | |||
2022 | 417 | def should_task_id_be_stored(self, task_id): | ||
2023 | 418 | ''' | ||
2024 | 419 | Helper function: Checks if a task should be stored in this backend | ||
2025 | 420 | @param task_id: a task id | ||
2026 | 421 | @returns bool: True if the task should be stored | ||
2027 | 422 | ''' | ||
2028 | 423 | task = self.req.get_task(task_id) | ||
2029 | 424 | return self.task_filter(task) | ||
2030 | 425 | |||
2031 | 426 | def queue_set_task(self, sender, tid): | ||
2032 | 223 | """ | 427 | """ |
2033 | 224 | Updates the task in the DataStore. Actually, it adds the task to a | 428 | Updates the task in the DataStore. Actually, it adds the task to a |
2034 | 225 | queue to be updated asynchronously. | 429 | queue to be updated asynchronously. |
2035 | 430 | @param sender: not used, any value will do. | ||
2036 | 226 | @param task: The Task object to be updated. | 431 | @param task: The Task object to be updated. |
2037 | 227 | """ | 432 | """ |
2047 | 228 | tid = task.get_id() | 433 | if self.should_task_id_be_stored(tid): |
2048 | 229 | if task not in self.to_set and tid not in self.to_remove: | 434 | if tid not in self.to_set and tid not in self.to_remove: |
2049 | 230 | self.to_set.append(task) | 435 | self.to_set.appendleft(tid) |
2050 | 231 | if self.lock.acquire(False): | 436 | self.__try_launch_setting_thread() |
2051 | 232 | func = self.setting_thread | 437 | else: |
2052 | 233 | t = threading.Thread(target=func) | 438 | self.queue_remove_task(None, tid) |
2044 | 234 | t.start() | ||
2045 | 235 | # else: | ||
2046 | 236 | # print "cannot acquire lock : not a problem, just for debug purpose" | ||
2053 | 237 | 439 | ||
2056 | 238 | def setting_thread(self): | 440 | def launch_setting_thread(self): |
2057 | 239 | """ | 441 | ''' |
2058 | 240 | Operates the threads to set and remove tasks. | 442 | Operates the threads to set and remove tasks. |
2059 | 241 | Releases the lock when it is done. | 443 | Releases the lock when it is done. |
2074 | 242 | """ | 444 | ''' |
2075 | 243 | try: | 445 | #FIXME: the lock should be general for all backends. Therefore, it |
2076 | 244 | while len(self.to_set) > 0: | 446 | #should be handled in the datastore |
2077 | 245 | t = self.to_set.pop(0) | 447 | while True: |
2078 | 246 | tid = t.get_id() | 448 | try: |
2079 | 247 | if tid not in self.to_remove: | 449 | tid = self.to_set.pop() |
2080 | 248 | self.count_set += 1 | 450 | except IndexError: |
2081 | 249 | #print "saving task %s (%s saves)" %(tid,self.count_set) | 451 | break |
2082 | 250 | self.backend.set_task(t) | 452 | #we check that the task is not already marked for deletion |
2083 | 251 | while len(self.to_remove) > 0: | 453 | #and that it's still to be stored in this backend |
2084 | 252 | tid = self.to_remove.pop(0) | 454 | #NOTE: no need to lock, we're reading |
2085 | 253 | self.backend.remove_task(tid) | 455 | if tid not in self.to_remove and \ |
2086 | 254 | finally: | 456 | self.should_task_id_be_stored(tid) and \ |
2087 | 255 | self.lock.release() | 457 | self.req.has_task(tid): |
2088 | 458 | task = self.req.get_task(tid) | ||
2089 | 459 | self.backend.queue_set_task(task) | ||
2090 | 460 | while True: | ||
2091 | 461 | try: | ||
2092 | 462 | tid = self.to_remove.pop() | ||
2093 | 463 | except IndexError: | ||
2094 | 464 | break | ||
2095 | 465 | self.backend.queue_remove_task(tid) | ||
2096 | 466 | #we release the weak lock | ||
2097 | 467 | self.to_set_timer = None | ||
2098 | 256 | 468 | ||
2101 | 257 | def remove_task(self, tid): | 469 | def queue_remove_task(self, sender, tid): |
2102 | 258 | """ | 470 | ''' |
2103 | 259 | Queues task to be removed. | 471 | Queues task to be removed. |
2104 | 472 | @param sender: not used, any value will do | ||
2105 | 260 | @param tid: The Task ID of the task to be removed | 473 | @param tid: The Task ID of the task to be removed |
2107 | 261 | """ | 474 | ''' |
2108 | 262 | if tid not in self.to_remove: | 475 | if tid not in self.to_remove: |
2131 | 263 | self.to_remove.append(tid) | 476 | self.to_remove.appendleft(tid) |
2132 | 264 | if self.lock.acquire(False): | 477 | self.__try_launch_setting_thread() |
2133 | 265 | func = self.setting_thread | 478 | |
2134 | 266 | t = threading.Thread(target=func) | 479 | def __try_launch_setting_thread(self): |
2135 | 267 | t.start() | 480 | ''' |
2136 | 268 | 481 | Helper function to launch the setting thread, if it's not running | |
2137 | 269 | def new_task_id(self): | 482 | ''' |
2138 | 270 | """ | 483 | if self.to_set_timer == None: |
2139 | 271 | returns a new ID created by the backend. | 484 | self.to_set_timer = threading.Timer(self.timer_timestep, \ |
2140 | 272 | """ | 485 | self.launch_setting_thread) |
2141 | 273 | return self.backend.new_task_id() | 486 | self.to_set_timer.start() |
2142 | 274 | 487 | ||
2143 | 275 | def quit(self): | 488 | def initialize(self, connect_signals = True): |
2144 | 276 | """ Quits the backend """ | 489 | ''' |
2145 | 277 | self.backend.quit() | 490 | Initializes the backend and starts looking for signals. |
2146 | 278 | 491 | @param connect_signals: if True, it starts listening for signals | |
2147 | 279 | #Those functions are only for TaskSource | 492 | ''' |
2148 | 280 | def get_parameters(self): | 493 | self.backend.initialize() |
2149 | 281 | """ | 494 | if connect_signals: |
2150 | 282 | Returns the parameters specified during creation of the DataStore | 495 | self._connect_signals() |
2151 | 283 | """ | 496 | |
2152 | 284 | return self.dic | 497 | def _connect_signals(self): |
2153 | 498 | ''' | ||
2154 | 499 | Helper function to connect signals | ||
2155 | 500 | ''' | ||
2156 | 501 | if not self.set_task_handle: | ||
2157 | 502 | self.set_task_handle = self.req.connect('task-modified', \ | ||
2158 | 503 | self.queue_set_task) | ||
2159 | 504 | if not self.remove_task_handle: | ||
2160 | 505 | self.remove_task_handle = self.req.connect('task-deleted',\ | ||
2161 | 506 | self.queue_remove_task) | ||
2162 | 507 | |||
2163 | 508 | def _disconnect_signals(self): | ||
2164 | 509 | ''' | ||
2165 | 510 | Helper function to disconnect signals | ||
2166 | 511 | ''' | ||
2167 | 512 | if self.set_task_handle: | ||
2168 | 513 | self.req.disconnect(self.set_task_handle) | ||
2169 | 514 | self.set_task_handle = None | ||
2170 | 515 | if self.remove_task_handle: | ||
2171 | 516 | self.req.disconnect(self.remove_task_handle) | ||
2172 | 517 | self.remove_task_handle = None | ||
2173 | 518 | |||
2174 | 519 | def sync(self): | ||
2175 | 520 | ''' | ||
2176 | 521 | Forces the TaskSource to sync all the pending tasks | ||
2177 | 522 | ''' | ||
2178 | 523 | if self.to_set_timer != None: | ||
2179 | 524 | try: | ||
2180 | 525 | self.to_set_timer.cancel() | ||
2181 | 526 | except: | ||
2182 | 527 | pass | ||
2183 | 528 | try: | ||
2184 | 529 | self.to_set_timer.join(5) | ||
2185 | 530 | except: | ||
2186 | 531 | pass | ||
2187 | 532 | self.launch_setting_thread() | ||
2188 | 533 | |||
2189 | 534 | def quit(self, disable = False): | ||
2190 | 535 | ''' | ||
2191 | 536 | Quits the backend and disconnect the signals | ||
2192 | 537 | @param disable: if True, the backend is disabled. | ||
2193 | 538 | ''' | ||
2194 | 539 | self._disconnect_signals() | ||
2195 | 540 | self.sync() | ||
2196 | 541 | self.backend.quit(disable) | ||
2197 | 542 | |||
2198 | 543 | def __getattr__(self, attr): | ||
2199 | 544 | ''' | ||
2200 | 545 | Delegates all the functions not defined here to the real backend | ||
2201 | 546 | (standard python function) | ||
2202 | 547 | @param attr: attribute to get | ||
2203 | 548 | ''' | ||
2204 | 549 | if attr in self.__dict__: | ||
2205 | 550 | return self.__dict__[attr] | ||
2206 | 551 | else: | ||
2207 | 552 | return getattr(self.backend, attr) | ||
2208 | 553 | |||
2209 | 554 | |||
2210 | 555 | |||
2211 | 556 | class FilteredDataStore(Borg): | ||
2212 | 557 | ''' | ||
2213 | 558 | This class acts as an interface to the Datastore. | ||
2214 | 559 | It is used to hide most of the methods of the Datastore. | ||
2215 | 560 | The backends can safely use the remaining methods. | ||
2216 | 561 | ''' | ||
2217 | 562 | |||
2218 | 563 | |||
2219 | 564 | def __init__(self, datastore): | ||
2220 | 565 | super(FilteredDataStore, self).__init__() | ||
2221 | 566 | self.datastore = datastore | ||
2222 | 567 | |||
2223 | 568 | def __getattr__(self, attr): | ||
2224 | 569 | if attr in ['task_factory', \ | ||
2225 | 570 | 'push_task', | ||
2226 | 571 | 'get_task', | ||
2227 | 572 | 'has_task', | ||
2228 | 573 | 'request_task_deletion']: | ||
2229 | 574 | return getattr(self.datastore, attr) | ||
2230 | 575 | else: | ||
2231 | 576 | raise AttributeError | ||
2232 | 577 | |||
2233 | 285 | 578 | ||
2234 | === modified file 'GTG/core/filters_bank.py' | |||
2235 | --- GTG/core/filters_bank.py 2010-06-14 19:30:50 +0000 | |||
2236 | +++ GTG/core/filters_bank.py 2010-06-23 01:19:23 +0000 | |||
2237 | @@ -35,6 +35,10 @@ | |||
2238 | 35 | 35 | ||
2239 | 36 | def set_parameters(self,dic): | 36 | def set_parameters(self,dic): |
2240 | 37 | self.dic = dic | 37 | self.dic = dic |
2241 | 38 | |||
2242 | 39 | def get_function(self): | ||
2243 | 40 | '''Returns the filtering function''' | ||
2244 | 41 | return self.func | ||
2245 | 38 | 42 | ||
2246 | 39 | def is_displayed(self,tid): | 43 | def is_displayed(self,tid): |
2247 | 40 | task = self.req.get_task(tid) | 44 | task = self.req.get_task(tid) |
2248 | @@ -152,6 +156,9 @@ | |||
2249 | 152 | #worklate | 156 | #worklate |
2250 | 153 | filt_obj = Filter(self.worklate,self.req) | 157 | filt_obj = Filter(self.worklate,self.req) |
2251 | 154 | self.available_filters['worklate'] = filt_obj | 158 | self.available_filters['worklate'] = filt_obj |
2252 | 159 | #backend filter | ||
2253 | 160 | filt_obj = Filter(self.backend_filter, self.req) | ||
2254 | 161 | self.available_filters['backend_filter'] = filt_obj | ||
2255 | 155 | #no_disabled_tag | 162 | #no_disabled_tag |
2256 | 156 | filt_obj = Filter(self.no_disabled_tag,self.req) | 163 | filt_obj = Filter(self.no_disabled_tag,self.req) |
2257 | 157 | param = {} | 164 | param = {} |
2258 | @@ -234,6 +241,19 @@ | |||
2259 | 234 | """ Filter of tasks which are closed """ | 241 | """ Filter of tasks which are closed """ |
2260 | 235 | ret = task.get_status() in [Task.STA_DISMISSED, Task.STA_DONE] | 242 | ret = task.get_status() in [Task.STA_DISMISSED, Task.STA_DONE] |
2261 | 236 | return ret | 243 | return ret |
2262 | 244 | |||
2263 | 245 | def backend_filter(self, task, tags_to_match_set): | ||
2264 | 246 | ''' | ||
2265 | 247 | Filter that checks if two tags sets intersect. It is used to check if a | ||
2266 | 248 | task should be stored inside a backend | ||
2267 | 249 | @param task: a task object | ||
2268 | 250 | @oaram tags_to_match_set: a *set* of tag names | ||
2269 | 251 | ''' | ||
2270 | 252 | all_tasks_tag = self.req.get_alltag_tag().get_name() | ||
2271 | 253 | if all_tasks_tag in tags_to_match_set: | ||
2272 | 254 | return True | ||
2273 | 255 | task_tags = set(task.get_tags_name()) | ||
2274 | 256 | return task_tags.intersection(tags_to_match_set) | ||
2275 | 237 | 257 | ||
2276 | 238 | def no_disabled_tag(self,task,parameters=None): | 258 | def no_disabled_tag(self,task,parameters=None): |
2277 | 239 | """Filter of task that don't have any disabled/nonworkview tag""" | 259 | """Filter of task that don't have any disabled/nonworkview tag""" |
2278 | 240 | 260 | ||
2279 | === modified file 'GTG/core/requester.py' | |||
2280 | --- GTG/core/requester.py 2010-06-21 12:34:23 +0000 | |||
2281 | +++ GTG/core/requester.py 2010-06-23 01:19:23 +0000 | |||
2282 | @@ -1,6 +1,6 @@ | |||
2283 | 1 | # -*- coding: utf-8 -*- | 1 | # -*- coding: utf-8 -*- |
2284 | 2 | # ----------------------------------------------------------------------------- | 2 | # ----------------------------------------------------------------------------- |
2286 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | 3 | # Getting Things Gnome! - a personal organizer for the GNOME desktop |
2287 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau |
2288 | 5 | # | 5 | # |
2289 | 6 | # This program is free software: you can redistribute it and/or modify it under | 6 | # This program is free software: you can redistribute it and/or modify it under |
2290 | @@ -39,18 +39,18 @@ | |||
2291 | 39 | Multiple L{Requester}s can exist on the same datastore, so they should | 39 | Multiple L{Requester}s can exist on the same datastore, so they should |
2292 | 40 | never have state of their own. | 40 | never have state of their own. |
2293 | 41 | """ | 41 | """ |
2306 | 42 | __gsignals__ = {'task-added': (gobject.SIGNAL_RUN_FIRST, \ | 42 | |
2307 | 43 | gobject.TYPE_NONE, (str, )), | 43 | __string_signal__ = (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, (str, )) |
2308 | 44 | 'task-deleted': (gobject.SIGNAL_RUN_FIRST, \ | 44 | |
2309 | 45 | gobject.TYPE_NONE, (str, )), | 45 | __gsignals__ = {'task-added' : __string_signal__, \ |
2310 | 46 | 'task-modified': (gobject.SIGNAL_RUN_FIRST, \ | 46 | 'task-deleted' : __string_signal__, \ |
2311 | 47 | gobject.TYPE_NONE, (str, )), | 47 | 'task-modified' : __string_signal__, \ |
2312 | 48 | 'tag-added': (gobject.SIGNAL_RUN_FIRST, \ | 48 | 'task-tagged' : __string_signal__, \ |
2313 | 49 | gobject.TYPE_NONE, (str, )), | 49 | 'task-untagged' : __string_signal__, \ |
2314 | 50 | 'tag-deleted': (gobject.SIGNAL_RUN_FIRST, \ | 50 | 'tag-added' : __string_signal__, \ |
2315 | 51 | gobject.TYPE_NONE, (str, )), | 51 | 'tag-deleted' : __string_signal__, \ |
2316 | 52 | 'tag-modified': (gobject.SIGNAL_RUN_FIRST, \ | 52 | 'tag-path-deleted' : __string_signal__, \ |
2317 | 53 | gobject.TYPE_NONE, (str, ))} | 53 | 'tag-modified' : __string_signal__} |
2318 | 54 | 54 | ||
2319 | 55 | def __init__(self, datastore): | 55 | def __init__(self, datastore): |
2320 | 56 | """Construct a L{Requester}.""" | 56 | """Construct a L{Requester}.""" |
2321 | @@ -72,12 +72,19 @@ | |||
2322 | 72 | self.counter_call += 1 | 72 | self.counter_call += 1 |
2323 | 73 | #print "signal task_modified %s (%s modifications)" %(tid,self.counter_call) | 73 | #print "signal task_modified %s (%s modifications)" %(tid,self.counter_call) |
2324 | 74 | gobject.idle_add(self.emit, "task-modified", tid) | 74 | gobject.idle_add(self.emit, "task-modified", tid) |
2326 | 75 | 75 | ||
2327 | 76 | def _task_deleted(self, tid): | ||
2328 | 77 | #when this is emitted, task has *already* been deleted | ||
2329 | 78 | gobject.idle_add(self.emit, "task-deleted", tid) | ||
2330 | 79 | |||
2331 | 76 | def _tag_added(self,tagname): | 80 | def _tag_added(self,tagname): |
2332 | 77 | gobject.idle_add(self.emit, "tag-added", tagname) | 81 | gobject.idle_add(self.emit, "tag-added", tagname) |
2333 | 78 | 82 | ||
2334 | 79 | def _tag_modified(self,tagname): | 83 | def _tag_modified(self,tagname): |
2335 | 80 | gobject.idle_add(self.emit, "tag-modified", tagname) | 84 | gobject.idle_add(self.emit, "tag-modified", tagname) |
2336 | 85 | |||
2337 | 86 | def _tag_path_deleted(self, path): | ||
2338 | 87 | gobject.idle_add(self.emit, "tag-path-deleted", path) | ||
2339 | 81 | 88 | ||
2340 | 82 | def _tag_deleted(self,tagname): | 89 | def _tag_deleted(self,tagname): |
2341 | 83 | gobject.idle_add(self.emit, "tag-deleted", tagname) | 90 | gobject.idle_add(self.emit, "tag-deleted", tagname) |
2342 | @@ -126,6 +133,7 @@ | |||
2343 | 126 | 133 | ||
2344 | 127 | ######### Filters bank ####################### | 134 | ######### Filters bank ####################### |
2345 | 128 | # Get the filter object for a given name | 135 | # Get the filter object for a given name |
2346 | 136 | |||
2347 | 129 | def get_filter(self,filter_name): | 137 | def get_filter(self,filter_name): |
2348 | 130 | return self.filters.get_filter(filter_name) | 138 | return self.filters.get_filter(filter_name) |
2349 | 131 | 139 | ||
2350 | @@ -162,7 +170,7 @@ | |||
2351 | 162 | task = self.ds.get_task(tid) | 170 | task = self.ds.get_task(tid) |
2352 | 163 | return task | 171 | return task |
2353 | 164 | 172 | ||
2355 | 165 | def new_task(self, pid=None, tags=None, newtask=True): | 173 | def new_task(self, tags=None, newtask=True): |
2356 | 166 | """Create a new task. | 174 | """Create a new task. |
2357 | 167 | 175 | ||
2358 | 168 | Note: this modifies the datastore. | 176 | Note: this modifies the datastore. |
2359 | @@ -175,11 +183,12 @@ | |||
2360 | 175 | existed, C{False} if importing an existing task from a backend. | 183 | existed, C{False} if importing an existing task from a backend. |
2361 | 176 | @return: A task from the data store | 184 | @return: A task from the data store |
2362 | 177 | """ | 185 | """ |
2364 | 178 | task = self.ds.new_task(pid=pid) | 186 | task = self.ds.new_task() |
2365 | 179 | if tags: | 187 | if tags: |
2366 | 180 | for t in tags: | 188 | for t in tags: |
2367 | 181 | assert(isinstance(t, Tag) == False) | 189 | assert(isinstance(t, Tag) == False) |
2368 | 182 | task.tag_added(t) | 190 | task.tag_added(t) |
2369 | 191 | self._task_loaded(task.get_id()) | ||
2370 | 183 | return task | 192 | return task |
2371 | 184 | 193 | ||
2372 | 185 | def delete_task(self, tid): | 194 | def delete_task(self, tid): |
2373 | @@ -196,11 +205,11 @@ | |||
2374 | 196 | for tag in task.get_tags(): | 205 | for tag in task.get_tags(): |
2375 | 197 | self.emit('tag-modified', tag.get_name()) | 206 | self.emit('tag-modified', tag.get_name()) |
2376 | 198 | self.emit('task-deleted', tid) | 207 | self.emit('task-deleted', tid) |
2379 | 199 | #return True | 208 | return self.basetree.remove_node(tid) |
2378 | 200 | return self.ds.delete_task(tid) | ||
2380 | 201 | 209 | ||
2381 | 202 | ############### Tags ########################## | 210 | ############### Tags ########################## |
2382 | 203 | ############################################### | 211 | ############################################### |
2383 | 212 | |||
2384 | 204 | def get_tag_tree(self): | 213 | def get_tag_tree(self): |
2385 | 205 | return self.ds.get_tagstore() | 214 | return self.ds.get_tagstore() |
2386 | 206 | 215 | ||
2387 | @@ -251,3 +260,27 @@ | |||
2388 | 251 | l.append(t.get_name()) | 260 | l.append(t.get_name()) |
2389 | 252 | l.sort(cmp=lambda x, y: cmp(x.lower(),y.lower())) | 261 | l.sort(cmp=lambda x, y: cmp(x.lower(),y.lower())) |
2390 | 253 | return l | 262 | return l |
2391 | 263 | |||
2392 | 264 | ############## Backends ####################### | ||
2393 | 265 | ############################################### | ||
2394 | 266 | |||
2395 | 267 | def get_all_backends(self, disabled = False): | ||
2396 | 268 | return self.ds.get_all_backends(disabled) | ||
2397 | 269 | |||
2398 | 270 | def register_backend(self, dic): | ||
2399 | 271 | return self.ds.register_backend(dic) | ||
2400 | 272 | |||
2401 | 273 | def flush_all_tasks(self, backend_id): | ||
2402 | 274 | return self.ds.flush_all_tasks(backend_id) | ||
2403 | 275 | |||
2404 | 276 | def get_backend(self, backend_id): | ||
2405 | 277 | return self.ds.get_backend(backend_id) | ||
2406 | 278 | |||
2407 | 279 | def set_backend_enabled(self, backend_id, state): | ||
2408 | 280 | return self.ds.set_backend_enabled(backend_id, state) | ||
2409 | 281 | |||
2410 | 282 | def remove_backend(self, backend_id): | ||
2411 | 283 | return self.ds.remove_backend(backend_id) | ||
2412 | 284 | |||
2413 | 285 | def backend_change_attached_tags(self, backend_id, tags): | ||
2414 | 286 | return self.ds.backend_change_attached_tags(backend_id, tags) | ||
2415 | 254 | 287 | ||
2416 | === modified file 'GTG/core/tagstore.py' | |||
2417 | --- GTG/core/tagstore.py 2010-06-12 13:31:18 +0000 | |||
2418 | +++ GTG/core/tagstore.py 2010-06-23 01:19:23 +0000 | |||
2419 | @@ -40,6 +40,7 @@ | |||
2420 | 40 | # There's only one Tag store by user. It will store all the tag used | 40 | # There's only one Tag store by user. It will store all the tag used |
2421 | 41 | # and their attribute. | 41 | # and their attribute. |
2422 | 42 | class TagStore(Tree): | 42 | class TagStore(Tree): |
2423 | 43 | |||
2424 | 43 | 44 | ||
2425 | 44 | def __init__(self,requester): | 45 | def __init__(self,requester): |
2426 | 45 | Tree.__init__(self) | 46 | Tree.__init__(self) |
2427 | @@ -50,7 +51,7 @@ | |||
2428 | 50 | 51 | ||
2429 | 51 | ### building the initial tags | 52 | ### building the initial tags |
2430 | 52 | # Build the "all tasks tag" | 53 | # Build the "all tasks tag" |
2432 | 53 | self.alltag_tag = self.new_tag("gtg-tags-all") | 54 | self.alltag_tag = self.new_tag(CoreConfig.ALLTASKS_TAG) |
2433 | 54 | self.alltag_tag.set_attribute("special","all") | 55 | self.alltag_tag.set_attribute("special","all") |
2434 | 55 | self.alltag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ | 56 | self.alltag_tag.set_attribute("label","<span weight='bold'>%s</span>"\ |
2435 | 56 | % _("All tasks")) | 57 | % _("All tasks")) |
2436 | @@ -68,7 +69,7 @@ | |||
2437 | 68 | self.sep_tag.set_attribute("special","sep") | 69 | self.sep_tag.set_attribute("special","sep") |
2438 | 69 | self.sep_tag.set_attribute("order",2) | 70 | self.sep_tag.set_attribute("order",2) |
2439 | 70 | 71 | ||
2441 | 71 | self.filename = os.path.join(CoreConfig.DATA_DIR, XMLFILE) | 72 | self.filename = os.path.join(CoreConfig().get_data_dir(), XMLFILE) |
2442 | 72 | doc, self.xmlstore = cleanxml.openxmlfile(self.filename, | 73 | doc, self.xmlstore = cleanxml.openxmlfile(self.filename, |
2443 | 73 | XMLROOT) #pylint: disable-msg=W0612 | 74 | XMLROOT) #pylint: disable-msg=W0612 |
2444 | 74 | for t in self.xmlstore.childNodes: | 75 | for t in self.xmlstore.childNodes: |
2445 | @@ -121,7 +122,7 @@ | |||
2446 | 121 | if tagname[0] != "@": | 122 | if tagname[0] != "@": |
2447 | 122 | tagname = "@" + tagname | 123 | tagname = "@" + tagname |
2448 | 123 | return self.get_node(tagname) | 124 | return self.get_node(tagname) |
2450 | 124 | 125 | ||
2451 | 125 | #FIXME : also add a new filter | 126 | #FIXME : also add a new filter |
2452 | 126 | def rename_tag(self, oldname, newname): | 127 | def rename_tag(self, oldname, newname): |
2453 | 127 | if len(newname) > 0 and \ | 128 | if len(newname) > 0 and \ |
2454 | @@ -316,9 +317,12 @@ | |||
2455 | 316 | def add_task(self, tid): | 317 | def add_task(self, tid): |
2456 | 317 | if tid not in self.tasks: | 318 | if tid not in self.tasks: |
2457 | 318 | self.tasks.append(tid) | 319 | self.tasks.append(tid) |
2459 | 319 | def remove_task(self,tid): | 320 | |
2460 | 321 | def remove_task(self, tid): | ||
2461 | 320 | if tid in self.tasks: | 322 | if tid in self.tasks: |
2462 | 321 | self.tasks.remove(tid) | 323 | self.tasks.remove(tid) |
2463 | 324 | self.req._tag_modified(self.get_name()) | ||
2464 | 325 | |||
2465 | 322 | def get_tasks(self): | 326 | def get_tasks(self): |
2466 | 323 | #return a copy of the list | 327 | #return a copy of the list |
2467 | 324 | toreturn = self.tasks[:] | 328 | toreturn = self.tasks[:] |
2468 | 325 | 329 | ||
2469 | === modified file 'GTG/core/task.py' | |||
2470 | --- GTG/core/task.py 2010-06-18 16:36:17 +0000 | |||
2471 | +++ GTG/core/task.py 2010-06-23 01:19:23 +0000 | |||
2472 | @@ -48,10 +48,10 @@ | |||
2473 | 48 | #tid is a string ! (we have to choose a type and stick to it) | 48 | #tid is a string ! (we have to choose a type and stick to it) |
2474 | 49 | self.tid = str(ze_id) | 49 | self.tid = str(ze_id) |
2475 | 50 | self.set_uuid(uuid.uuid4()) | 50 | self.set_uuid(uuid.uuid4()) |
2476 | 51 | self.remote_ids = {} | ||
2477 | 51 | self.content = "" | 52 | self.content = "" |
2478 | 52 | #self.content = \ | 53 | #self.content = \ |
2479 | 53 | # "<content>Press Escape or close this task to save it</content>" | 54 | # "<content>Press Escape or close this task to save it</content>" |
2480 | 54 | self.sync_func = None | ||
2481 | 55 | self.title = _("My new task") | 55 | self.title = _("My new task") |
2482 | 56 | #available status are: Active - Done - Dismiss - Note | 56 | #available status are: Active - Done - Dismiss - Note |
2483 | 57 | self.status = self.STA_ACTIVE | 57 | self.status = self.STA_ACTIVE |
2484 | @@ -78,7 +78,6 @@ | |||
2485 | 78 | self.loaded = True | 78 | self.loaded = True |
2486 | 79 | if signal: | 79 | if signal: |
2487 | 80 | self.req._task_loaded(self.tid) | 80 | self.req._task_loaded(self.tid) |
2488 | 81 | #not sure the following is necessary | ||
2489 | 82 | #self.req._task_modified(self.tid) | 81 | #self.req._task_modified(self.tid) |
2490 | 83 | 82 | ||
2491 | 84 | def set_to_keep(self): | 83 | def set_to_keep(self): |
2492 | @@ -102,6 +101,25 @@ | |||
2493 | 102 | self.sync() | 101 | self.sync() |
2494 | 103 | return self.uuid | 102 | return self.uuid |
2495 | 104 | 103 | ||
2496 | 104 | def get_remote_ids(self): | ||
2497 | 105 | ''' | ||
2498 | 106 | A task usually has a different id in all the different backends. | ||
2499 | 107 | This function returns a dictionary backend_id->the id the task has | ||
2500 | 108 | in that backend | ||
2501 | 109 | @returns dict: dictionary backend_id->task remote id | ||
2502 | 110 | ''' | ||
2503 | 111 | return self.remote_ids | ||
2504 | 112 | |||
2505 | 113 | def add_remote_id(self, backend_id, task_remote_id): | ||
2506 | 114 | ''' | ||
2507 | 115 | A task usually has a different id in all the different backends. | ||
2508 | 116 | This function adds a relationship backend_id-> remote_id that can be | ||
2509 | 117 | retrieved using get_remote_ids | ||
2510 | 118 | @param backend_id: string representing the backend id | ||
2511 | 119 | @param task_remote_id: the id for this task in the backend backend_id | ||
2512 | 120 | ''' | ||
2513 | 121 | self.remote_ids[str(backend_id)] = str(task_remote_id) | ||
2514 | 122 | |||
2515 | 105 | def get_title(self): | 123 | def get_title(self): |
2516 | 106 | return self.title | 124 | return self.title |
2517 | 107 | 125 | ||
2518 | @@ -115,7 +133,7 @@ | |||
2519 | 115 | self.title = title.strip('\t\n') | 133 | self.title = title.strip('\t\n') |
2520 | 116 | else: | 134 | else: |
2521 | 117 | self.title = "(no title task)" | 135 | self.title = "(no title task)" |
2523 | 118 | #Avoid unecessary sync | 136 | #Avoid unnecessary sync |
2524 | 119 | if self.title != old_title: | 137 | if self.title != old_title: |
2525 | 120 | self.sync() | 138 | self.sync() |
2526 | 121 | return True | 139 | return True |
2527 | @@ -294,8 +312,7 @@ | |||
2528 | 294 | """Add a newly created subtask to this task. Return the task added as | 312 | """Add a newly created subtask to this task. Return the task added as |
2529 | 295 | a subtask | 313 | a subtask |
2530 | 296 | """ | 314 | """ |
2533 | 297 | uid, pid = self.get_id().split('@') #pylint: disable-msg=W0612 | 315 | subt = self.req.new_task(newtask=True) |
2532 | 298 | subt = self.req.new_task(pid=pid, newtask=True) | ||
2534 | 299 | #we use the inherited childrens | 316 | #we use the inherited childrens |
2535 | 300 | self.add_child(subt.get_id()) | 317 | self.add_child(subt.get_id()) |
2536 | 301 | return subt | 318 | return subt |
2537 | @@ -427,37 +444,32 @@ | |||
2538 | 427 | #This method is called by the datastore and should not be called directly | 444 | #This method is called by the datastore and should not be called directly |
2539 | 428 | #Use the requester | 445 | #Use the requester |
2540 | 429 | def delete(self): | 446 | def delete(self): |
2542 | 430 | self.set_sync_func(None, callsync=False) | 447 | #we issue a delete for all the children |
2543 | 431 | for task in self.get_subtasks(): | 448 | for task in self.get_subtasks(): |
2546 | 432 | task.remove_parent(self.get_id()) | 449 | #I think it's superfluous (invernizzi) |
2547 | 433 | self.req.delete_task(task.get_id()) | 450 | #task.remove_parent(self.get_id()) |
2548 | 451 | task.delete() | ||
2549 | 452 | #we tell the parents we have to go | ||
2550 | 434 | for i in self.get_parents(): | 453 | for i in self.get_parents(): |
2551 | 435 | task = self.req.get_task(i) | 454 | task = self.req.get_task(i) |
2552 | 436 | task.remove_child(self.get_id()) | 455 | task.remove_child(self.get_id()) |
2553 | 456 | #we tell the tags about the deletion | ||
2554 | 437 | for tagname in self.tags: | 457 | for tagname in self.tags: |
2555 | 438 | tag = self.req.get_tag(tagname) | 458 | tag = self.req.get_tag(tagname) |
2556 | 439 | tag.remove_task(self.get_id()) | 459 | tag.remove_task(self.get_id()) |
2566 | 440 | #then we remove effectively the task | 460 | #then we signal the we are ready to be removed |
2567 | 441 | #self.req.delete_task(self.get_id()) | 461 | self.req._task_deleted(self.get_id()) |
2559 | 442 | |||
2560 | 443 | #This is a callback. The "sync" function has to be set | ||
2561 | 444 | def set_sync_func(self, sync, callsync=True): | ||
2562 | 445 | self.sync_func = sync | ||
2563 | 446 | #We call it immediatly to save stuffs that were set before this | ||
2564 | 447 | if callsync and self.is_loaded(): | ||
2565 | 448 | self.sync() | ||
2568 | 449 | 462 | ||
2569 | 450 | def sync(self): | 463 | def sync(self): |
2570 | 451 | self._modified_update() | 464 | self._modified_update() |
2573 | 452 | if self.sync_func and self.is_loaded(): | 465 | if self.is_loaded(): |
2572 | 453 | self.sync_func(self) | ||
2574 | 454 | self.call_modified() | 466 | self.call_modified() |
2575 | 455 | return True | 467 | return True |
2576 | 456 | else: | 468 | else: |
2577 | 457 | return False | 469 | return False |
2578 | 458 | 470 | ||
2579 | 459 | #This function send the modified signals for the tasks, | 471 | #This function send the modified signals for the tasks, |
2581 | 460 | #parents and childrens | 472 | #parents and children |
2582 | 461 | def call_modified(self): | 473 | def call_modified(self): |
2583 | 462 | #we first modify children | 474 | #we first modify children |
2584 | 463 | for s in self.get_children(): | 475 | for s in self.get_children(): |
2585 | @@ -469,10 +481,11 @@ | |||
2586 | 469 | self.req._task_modified(p) | 481 | self.req._task_modified(p) |
2587 | 470 | 482 | ||
2588 | 471 | def _modified_update(self): | 483 | def _modified_update(self): |
2589 | 484 | ''' | ||
2590 | 485 | Updates the modified timestamp | ||
2591 | 486 | ''' | ||
2592 | 472 | self.modified = datetime.now() | 487 | self.modified = datetime.now() |
2593 | 473 | 488 | ||
2594 | 474 | |||
2595 | 475 | |||
2596 | 476 | ### TAG FUNCTIONS ############################################################ | 489 | ### TAG FUNCTIONS ############################################################ |
2597 | 477 | # | 490 | # |
2598 | 478 | def get_tags_name(self): | 491 | def get_tags_name(self): |
2599 | @@ -509,6 +522,8 @@ | |||
2600 | 509 | #Do not add the same tag twice | 522 | #Do not add the same tag twice |
2601 | 510 | if not t in self.tags: | 523 | if not t in self.tags: |
2602 | 511 | self.tags.append(t) | 524 | self.tags.append(t) |
2603 | 525 | #we notify the backends | ||
2604 | 526 | #self.req.tag_was_added_to_task(self, tagname) | ||
2605 | 512 | for child in self.get_subtasks(): | 527 | for child in self.get_subtasks(): |
2606 | 513 | if child.can_be_deleted: | 528 | if child.can_be_deleted: |
2607 | 514 | child.add_tag(t) | 529 | child.add_tag(t) |
2608 | 515 | 530 | ||
2609 | === modified file 'GTG/gtg.py' | |||
2610 | --- GTG/gtg.py 2010-06-18 11:55:03 +0000 | |||
2611 | +++ GTG/gtg.py 2010-06-23 01:19:23 +0000 | |||
2612 | @@ -45,18 +45,17 @@ | |||
2613 | 45 | """This is the top-level exec script for running GTG""" | 45 | """This is the top-level exec script for running GTG""" |
2614 | 46 | 46 | ||
2615 | 47 | #=== IMPORT =================================================================== | 47 | #=== IMPORT =================================================================== |
2616 | 48 | from contextlib import contextmanager | ||
2617 | 49 | import os | 48 | import os |
2618 | 50 | import logging | 49 | import logging |
2619 | 51 | import signal | ||
2620 | 52 | 50 | ||
2621 | 53 | import dbus | 51 | import dbus |
2622 | 54 | 52 | ||
2623 | 55 | #our own imports | 53 | #our own imports |
2625 | 56 | from GTG import _, info | 54 | from GTG.backends import BackendFactory |
2626 | 55 | from GTG import _ | ||
2627 | 57 | from GTG.core import CoreConfig | 56 | from GTG.core import CoreConfig |
2628 | 58 | from GTG.core.datastore import DataStore | 57 | from GTG.core.datastore import DataStore |
2630 | 59 | from GTG.gtk import crashhandler | 58 | from GTG.gtk.crashhandler import signal_catcher |
2631 | 60 | from GTG.gtk.manager import Manager | 59 | from GTG.gtk.manager import Manager |
2632 | 61 | from GTG.tools.logger import Log | 60 | from GTG.tools.logger import Log |
2633 | 62 | 61 | ||
2634 | @@ -93,54 +92,55 @@ | |||
2635 | 93 | #=== MAIN CLASS =============================================================== | 92 | #=== MAIN CLASS =============================================================== |
2636 | 94 | 93 | ||
2637 | 95 | def main(options=None, args=None): | 94 | def main(options=None, args=None): |
2638 | 95 | ''' | ||
2639 | 96 | Calling this starts the full GTG experience ( :-D ) | ||
2640 | 97 | ''' | ||
2641 | 98 | config, ds, req = core_main_init(options, args) | ||
2642 | 99 | # Launch task browser | ||
2643 | 100 | manager = Manager(req, config) | ||
2644 | 101 | #main loop | ||
2645 | 102 | #To be more user friendly and get the logs of crashes, we show an apport | ||
2646 | 103 | # hooked window upon crashes | ||
2647 | 104 | with signal_catcher(manager.close_browser): | ||
2648 | 105 | manager.main() | ||
2649 | 106 | core_main_quit(config, ds) | ||
2650 | 107 | |||
2651 | 108 | def core_main_init(options = None, args = None): | ||
2652 | 109 | ''' | ||
2653 | 110 | Part of the main function prior to the UI initialization. | ||
2654 | 111 | ''' | ||
2655 | 96 | # Debugging subsystem initialization | 112 | # Debugging subsystem initialization |
2656 | 97 | if options.debug: | 113 | if options.debug: |
2657 | 98 | Log.setLevel(logging.DEBUG) | 114 | Log.setLevel(logging.DEBUG) |
2658 | 99 | Log.debug("Debug output enabled.") | 115 | Log.debug("Debug output enabled.") |
2660 | 100 | 116 | Log.set_debugging_mode(True) | |
2661 | 101 | config = CoreConfig() | 117 | config = CoreConfig() |
2671 | 102 | check_instance(config.DATA_DIR) | 118 | check_instance(config.get_data_dir()) |
2672 | 103 | backends_list = config.get_backends_list() | 119 | backends_list = BackendFactory().get_saved_backends_list() |
2664 | 104 | |||
2665 | 105 | #initialize Apport hook for crash handling | ||
2666 | 106 | crashhandler.initialize(app_name = "Getting Things GNOME!", message="GTG" | ||
2667 | 107 | + info.VERSION + _(" has crashed. Please report the bug on <a href=\"" | ||
2668 | 108 | "http://bugs.edge.launchpad.net/gtg\">our Launchpad page</a>. If you " | ||
2669 | 109 | "have Apport installed, it will be started for you."), use_apport = True) | ||
2670 | 110 | |||
2673 | 111 | # Load data store | 120 | # Load data store |
2674 | 112 | ds = DataStore() | 121 | ds = DataStore() |
2676 | 113 | 122 | # Register backends | |
2677 | 114 | for backend_dic in backends_list: | 123 | for backend_dic in backends_list: |
2678 | 115 | ds.register_backend(backend_dic) | 124 | ds.register_backend(backend_dic) |
2679 | 116 | |||
2680 | 117 | #save directly the backends to be sure to write projects.xml | 125 | #save directly the backends to be sure to write projects.xml |
2682 | 118 | config.save_datastore(ds,initial_save=True) | 126 | ds.save(quit = False) |
2683 | 119 | 127 | ||
2684 | 120 | # Launch task browser | 128 | # Launch task browser |
2685 | 121 | req = ds.get_requester() | 129 | req = ds.get_requester() |
2696 | 122 | manager = Manager(req, config) | 130 | return config, ds, req |
2687 | 123 | |||
2688 | 124 | #we listen for signals from the system in order to save our configuration | ||
2689 | 125 | # if GTG is forcefully terminated (e.g.: on shutdown). | ||
2690 | 126 | @contextmanager | ||
2691 | 127 | def signal_catcher(): | ||
2692 | 128 | #if TERM or ABORT are caught, we close the browser | ||
2693 | 129 | for s in [signal.SIGABRT, signal.SIGTERM]: | ||
2694 | 130 | signal.signal(s, lambda a,b: manager.close_browser()) | ||
2695 | 131 | yield | ||
2697 | 132 | 131 | ||
2702 | 133 | #main loop | 132 | def core_main_quit(config, ds): |
2703 | 134 | with signal_catcher(): | 133 | ''' |
2704 | 135 | manager.main() | 134 | Last bits of code executed in GTG, after the UI has been shut off. |
2705 | 136 | 135 | Currently, it's just saving everything. | |
2706 | 136 | ''' | ||
2707 | 137 | # Ideally we should load window geometry configuration from a config. | 137 | # Ideally we should load window geometry configuration from a config. |
2708 | 138 | # backend like gconf at some point, and restore the appearance of the | 138 | # backend like gconf at some point, and restore the appearance of the |
2709 | 139 | # application as the user last exited it. | 139 | # application as the user last exited it. |
2711 | 140 | 140 | # | |
2712 | 141 | # Ending the application: we save configuration | 141 | # Ending the application: we save configuration |
2715 | 142 | config.save_config() | 142 | config.save() |
2716 | 143 | config.save_datastore(ds) | 143 | ds.save(quit = True) |
2717 | 144 | 144 | ||
2718 | 145 | #=== EXECUTION ================================================================ | 145 | #=== EXECUTION ================================================================ |
2719 | 146 | 146 | ||
2720 | 147 | 147 | ||
2721 | === modified file 'GTG/gtk/browser/browser.py' | |||
2722 | --- GTG/gtk/browser/browser.py 2010-06-21 12:34:23 +0000 | |||
2723 | +++ GTG/gtk/browser/browser.py 2010-06-23 01:19:23 +0000 | |||
2724 | @@ -23,7 +23,6 @@ | |||
2725 | 23 | #=== IMPORT =================================================================== | 23 | #=== IMPORT =================================================================== |
2726 | 24 | #system imports | 24 | #system imports |
2727 | 25 | import locale | 25 | import locale |
2728 | 26 | import os | ||
2729 | 27 | import re | 26 | import re |
2730 | 28 | import time | 27 | import time |
2731 | 29 | import webbrowser | 28 | import webbrowser |
2732 | @@ -35,18 +34,16 @@ | |||
2733 | 35 | 34 | ||
2734 | 36 | #our own imports | 35 | #our own imports |
2735 | 37 | import GTG | 36 | import GTG |
2736 | 37 | from GTG.core import CoreConfig | ||
2737 | 38 | from GTG import _, info, ngettext | 38 | from GTG import _, info, ngettext |
2738 | 39 | from GTG.core.task import Task | 39 | from GTG.core.task import Task |
2739 | 40 | #from GTG.core.tagstore import Tag | ||
2740 | 41 | from GTG.gtk.browser import GnomeConfig, tasktree, tagtree | 40 | from GTG.gtk.browser import GnomeConfig, tasktree, tagtree |
2741 | 42 | #from GTG.taskbrowser.preferences import PreferencesDialog | ||
2742 | 43 | from GTG.gtk.browser.tasktree import TaskTreeModel,\ | 41 | from GTG.gtk.browser.tasktree import TaskTreeModel,\ |
2743 | 44 | ActiveTaskTreeView,\ | 42 | ActiveTaskTreeView,\ |
2744 | 45 | ClosedTaskTreeView | 43 | ClosedTaskTreeView |
2745 | 46 | from GTG.gtk.browser.tagtree import TagTree | 44 | from GTG.gtk.browser.tagtree import TagTree |
2746 | 47 | from GTG.tools import openurl | 45 | from GTG.tools import openurl |
2749 | 48 | from GTG.tools.dates import strtodate,\ | 46 | from GTG.tools.dates import no_date,\ |
2748 | 49 | no_date,\ | ||
2750 | 50 | FuzzyDate, \ | 47 | FuzzyDate, \ |
2751 | 51 | get_canonical_date | 48 | get_canonical_date |
2752 | 52 | from GTG.tools.logger import Log | 49 | from GTG.tools.logger import Log |
2753 | @@ -159,7 +156,7 @@ | |||
2754 | 159 | self.priv['quick_add_cbs'] = [] | 156 | self.priv['quick_add_cbs'] = [] |
2755 | 160 | 157 | ||
2756 | 161 | def _init_icon_theme(self): | 158 | def _init_icon_theme(self): |
2758 | 162 | icon_dirs = [GTG.DATA_DIR, os.path.join(GTG.DATA_DIR, "icons")] | 159 | icon_dirs = CoreConfig().get_icons_directories() |
2759 | 163 | for i in icon_dirs: | 160 | for i in icon_dirs: |
2760 | 164 | gtk.icon_theme_get_default().prepend_search_path(i) | 161 | gtk.icon_theme_get_default().prepend_search_path(i) |
2761 | 165 | gtk.window_set_default_icon_name("gtg") | 162 | gtk.window_set_default_icon_name("gtg") |
2762 | @@ -429,7 +426,7 @@ | |||
2763 | 429 | ### HELPER FUNCTIONS ######################################################## | 426 | ### HELPER FUNCTIONS ######################################################## |
2764 | 430 | 427 | ||
2765 | 431 | def open_preferences(self,widget): | 428 | def open_preferences(self,widget): |
2767 | 432 | self.vmanager.show_preferences(self.priv) | 429 | self.vmanager.open_preferences(self.priv) |
2768 | 433 | 430 | ||
2769 | 434 | def quit(self,widget=None): | 431 | def quit(self,widget=None): |
2770 | 435 | self.vmanager.close_browser() | 432 | self.vmanager.close_browser() |
2771 | 436 | 433 | ||
2772 | === modified file 'GTG/gtk/browser/tagtree.py' | |||
2773 | --- GTG/gtk/browser/tagtree.py 2010-06-18 16:46:10 +0000 | |||
2774 | +++ GTG/gtk/browser/tagtree.py 2010-06-23 01:19:23 +0000 | |||
2775 | @@ -72,7 +72,8 @@ | |||
2776 | 72 | task = self.req.get_task(tid) | 72 | task = self.req.get_task(tid) |
2777 | 73 | if task: | 73 | if task: |
2778 | 74 | for tag in task.get_tags(): | 74 | for tag in task.get_tags(): |
2780 | 75 | self.tagrefresh(sender=sender,tagname=tag.get_name()) | 75 | if tag: |
2781 | 76 | self.tagrefresh(sender=sender,tagname=tag.get_name()) | ||
2782 | 76 | 77 | ||
2783 | 77 | def tagrefresh(self,sender=None,tagname=None): | 78 | def tagrefresh(self,sender=None,tagname=None): |
2784 | 78 | if tagname: | 79 | if tagname: |
2785 | 79 | 80 | ||
2786 | === modified file 'GTG/gtk/crashhandler.py' | |||
2787 | --- GTG/gtk/crashhandler.py 2010-06-07 21:14:45 +0000 | |||
2788 | +++ GTG/gtk/crashhandler.py 2010-06-23 01:19:23 +0000 | |||
2789 | @@ -33,6 +33,12 @@ | |||
2790 | 33 | import sys | 33 | import sys |
2791 | 34 | import os | 34 | import os |
2792 | 35 | import time | 35 | import time |
2793 | 36 | import signal | ||
2794 | 37 | from contextlib import contextmanager | ||
2795 | 38 | |||
2796 | 39 | from GTG import info | ||
2797 | 40 | |||
2798 | 41 | |||
2799 | 36 | try: | 42 | try: |
2800 | 37 | import pygtk | 43 | import pygtk |
2801 | 38 | pygtk.require("2.0") # not tested on earlier versions | 44 | pygtk.require("2.0") # not tested on earlier versions |
2802 | @@ -297,3 +303,21 @@ | |||
2803 | 297 | return "gtkcrashhandler.py should imported, not run" | 303 | return "gtkcrashhandler.py should imported, not run" |
2804 | 298 | raise DoNotRunException() | 304 | raise DoNotRunException() |
2805 | 299 | 305 | ||
2806 | 306 | |||
2807 | 307 | ## We handle initialization directly here, since this module will be used as a | ||
2808 | 308 | # singleton | ||
2809 | 309 | #we listen for signals from the system in order to save our configuration | ||
2810 | 310 | # if GTG is forcefully terminated (e.g.: on shutdown). | ||
2811 | 311 | @contextmanager | ||
2812 | 312 | def signal_catcher(callback): | ||
2813 | 313 | #if TERM or ABORT are caught, we execute the callback function | ||
2814 | 314 | for s in [signal.SIGABRT, signal.SIGTERM]: | ||
2815 | 315 | signal.signal(s, lambda a,b: callback()) | ||
2816 | 316 | yield | ||
2817 | 317 | |||
2818 | 318 | initialize(app_name = "Getting Things GNOME!", | ||
2819 | 319 | message = "GTG" + info.VERSION + | ||
2820 | 320 | _(" has crashed. Please report the bug on <a "\ | ||
2821 | 321 | "href=\"http://bugs.edge.launchpad.net/gtg\">our Launchpad page</a>."\ | ||
2822 | 322 | " If you have Apport installed, it will be started for you."), \ | ||
2823 | 323 | use_apport = True) | ||
2824 | 300 | 324 | ||
2825 | === modified file 'GTG/gtk/delete_dialog.py' | |||
2826 | --- GTG/gtk/delete_dialog.py 2010-06-23 00:38:13 +0000 | |||
2827 | +++ GTG/gtk/delete_dialog.py 2010-06-23 01:19:23 +0000 | |||
2828 | @@ -43,7 +43,11 @@ | |||
2829 | 43 | """if we pass a tid as a parameter, we delete directly | 43 | """if we pass a tid as a parameter, we delete directly |
2830 | 44 | otherwise, we will look which tid is selected""" | 44 | otherwise, we will look which tid is selected""" |
2831 | 45 | for tid in self.tids_todelete: | 45 | for tid in self.tids_todelete: |
2833 | 46 | self.req.delete_task(tid) | 46 | task = self.req.get_task(tid) |
2834 | 47 | if task: | ||
2835 | 48 | task.delete() | ||
2836 | 49 | else: | ||
2837 | 50 | print "trying to delete task already deleted" | ||
2838 | 47 | self.tids_todelete = [] | 51 | self.tids_todelete = [] |
2839 | 48 | 52 | ||
2840 | 49 | def delete_tasks(self, tids=None): | 53 | def delete_tasks(self, tids=None): |
2841 | 50 | 54 | ||
2842 | === modified file 'GTG/gtk/editor/editor.py' | |||
2843 | --- GTG/gtk/editor/editor.py 2010-06-07 21:14:45 +0000 | |||
2844 | +++ GTG/gtk/editor/editor.py 2010-06-23 01:19:23 +0000 | |||
2845 | @@ -39,7 +39,7 @@ | |||
2846 | 39 | from GTG import _ | 39 | from GTG import _ |
2847 | 40 | from GTG import ngettext | 40 | from GTG import ngettext |
2848 | 41 | from GTG import PLUGIN_DIR | 41 | from GTG import PLUGIN_DIR |
2850 | 42 | from GTG import DATA_DIR | 42 | from GTG.core import CoreConfig |
2851 | 43 | from GTG.gtk.editor import GnomeConfig | 43 | from GTG.gtk.editor import GnomeConfig |
2852 | 44 | from GTG.gtk.editor.taskview import TaskView | 44 | from GTG.gtk.editor.taskview import TaskView |
2853 | 45 | from GTG.core.plugins.engine import PluginEngine | 45 | from GTG.core.plugins.engine import PluginEngine |
2854 | @@ -176,7 +176,7 @@ | |||
2855 | 176 | self.pengine = PluginEngine(PLUGIN_DIR) | 176 | self.pengine = PluginEngine(PLUGIN_DIR) |
2856 | 177 | self.te_plugin_api = PluginAPI(window = self.window, | 177 | self.te_plugin_api = PluginAPI(window = self.window, |
2857 | 178 | config = None, | 178 | config = None, |
2859 | 179 | data_dir = DATA_DIR, | 179 | data_dir = CoreConfig().get_data_dir(), |
2860 | 180 | builder = self.builder, | 180 | builder = self.builder, |
2861 | 181 | requester = self.req, | 181 | requester = self.req, |
2862 | 182 | tagpopup = None, | 182 | tagpopup = None, |
2863 | 183 | 183 | ||
2864 | === modified file 'GTG/gtk/manager.py' | |||
2865 | --- GTG/gtk/manager.py 2010-06-10 14:45:36 +0000 | |||
2866 | +++ GTG/gtk/manager.py 2010-06-23 01:19:23 +0000 | |||
2867 | @@ -40,7 +40,11 @@ | |||
2868 | 40 | from GTG.core.plugins.api import PluginAPI | 40 | from GTG.core.plugins.api import PluginAPI |
2869 | 41 | from GTG.tools.logger import Log | 41 | from GTG.tools.logger import Log |
2870 | 42 | 42 | ||
2871 | 43 | |||
2872 | 44 | |||
2873 | 43 | class Manager: | 45 | class Manager: |
2874 | 46 | |||
2875 | 47 | |||
2876 | 44 | ############## init ##################################################### | 48 | ############## init ##################################################### |
2877 | 45 | def __init__(self, req, config): | 49 | def __init__(self, req, config): |
2878 | 46 | self.config_obj = config | 50 | self.config_obj = config |
2879 | @@ -72,9 +76,9 @@ | |||
2880 | 72 | #Deletion UI | 76 | #Deletion UI |
2881 | 73 | self.delete_dialog = None | 77 | self.delete_dialog = None |
2882 | 74 | 78 | ||
2886 | 75 | #Preferences windows | 79 | #Preferences and Backends windows |
2887 | 76 | # Initialize "Preferences" dialog | 80 | # Initialize dialogs |
2888 | 77 | self.preferences = None | 81 | self.preferences_dialog = None |
2889 | 78 | 82 | ||
2890 | 79 | #DBus | 83 | #DBus |
2891 | 80 | DBusTaskWrapper(self.req, self) | 84 | DBusTaskWrapper(self.req, self) |
2892 | @@ -89,7 +93,7 @@ | |||
2893 | 89 | # initializes the plugin api class | 93 | # initializes the plugin api class |
2894 | 90 | self.plugin_api = PluginAPI(window = self.browser.window, | 94 | self.plugin_api = PluginAPI(window = self.browser.window, |
2895 | 91 | config = self.config, | 95 | config = self.config, |
2897 | 92 | data_dir = GTG.DATA_DIR, | 96 | data_dir = self.config_obj.get_data_dir(), |
2898 | 93 | builder = self.browser.builder, | 97 | builder = self.browser.builder, |
2899 | 94 | requester = self.req, | 98 | requester = self.req, |
2900 | 95 | tagpopup = self.browser.tagpopup, | 99 | tagpopup = self.browser.tagpopup, |
2901 | @@ -189,8 +193,8 @@ | |||
2902 | 189 | 193 | ||
2903 | 190 | ################ Others dialog ############################################ | 194 | ################ Others dialog ############################################ |
2904 | 191 | 195 | ||
2907 | 192 | def show_preferences(self, config_priv, sender=None): | 196 | def open_preferences(self, config_priv, sender=None): |
2908 | 193 | if not self.preferences: | 197 | if not hasattr(self, "preferences"): |
2909 | 194 | self.preferences = PreferencesDialog(self.pengine, self.p_apis, \ | 198 | self.preferences = PreferencesDialog(self.pengine, self.p_apis, \ |
2910 | 195 | self.config_obj) | 199 | self.config_obj) |
2911 | 196 | self.preferences.activate(config_priv) | 200 | self.preferences.activate(config_priv) |
2912 | 197 | 201 | ||
2913 | === modified file 'GTG/gtk/preferences.glade' | |||
2914 | --- GTG/gtk/preferences.glade 2010-06-02 18:12:23 +0000 | |||
2915 | +++ GTG/gtk/preferences.glade 2010-06-23 01:19:23 +0000 | |||
2916 | @@ -213,63 +213,10 @@ | |||
2917 | 213 | </packing> | 213 | </packing> |
2918 | 214 | </child> | 214 | </child> |
2919 | 215 | <child> | 215 | <child> |
2967 | 216 | <object class="GtkAlignment" id="prefs-alignment3"> | 216 | <placeholder/> |
2921 | 217 | <property name="visible">True</property> | ||
2922 | 218 | <property name="top_padding">10</property> | ||
2923 | 219 | <property name="bottom_padding">10</property> | ||
2924 | 220 | <property name="left_padding">10</property> | ||
2925 | 221 | <property name="right_padding">10</property> | ||
2926 | 222 | <child> | ||
2927 | 223 | <object class="GtkVBox" id="prefs-vbox5"> | ||
2928 | 224 | <property name="visible">True</property> | ||
2929 | 225 | <property name="spacing">6</property> | ||
2930 | 226 | <child> | ||
2931 | 227 | <object class="GtkLabel" id="prefs-label6"> | ||
2932 | 228 | <property name="visible">True</property> | ||
2933 | 229 | <property name="xalign">0</property> | ||
2934 | 230 | <property name="label" translatable="yes">Task _Backends:</property> | ||
2935 | 231 | <property name="use_underline">True</property> | ||
2936 | 232 | </object> | ||
2937 | 233 | <packing> | ||
2938 | 234 | <property name="expand">False</property> | ||
2939 | 235 | <property name="position">0</property> | ||
2940 | 236 | </packing> | ||
2941 | 237 | </child> | ||
2942 | 238 | <child> | ||
2943 | 239 | <object class="GtkScrolledWindow" id="prefs-scrolledwindow1"> | ||
2944 | 240 | <property name="visible">True</property> | ||
2945 | 241 | <property name="sensitive">False</property> | ||
2946 | 242 | <property name="can_focus">True</property> | ||
2947 | 243 | <property name="hscrollbar_policy">never</property> | ||
2948 | 244 | <property name="vscrollbar_policy">automatic</property> | ||
2949 | 245 | <child> | ||
2950 | 246 | <object class="GtkTreeView" id="BackendTree"> | ||
2951 | 247 | <property name="visible">True</property> | ||
2952 | 248 | <property name="sensitive">False</property> | ||
2953 | 249 | <property name="can_focus">True</property> | ||
2954 | 250 | </object> | ||
2955 | 251 | </child> | ||
2956 | 252 | </object> | ||
2957 | 253 | <packing> | ||
2958 | 254 | <property name="position">1</property> | ||
2959 | 255 | </packing> | ||
2960 | 256 | </child> | ||
2961 | 257 | </object> | ||
2962 | 258 | </child> | ||
2963 | 259 | </object> | ||
2964 | 260 | <packing> | ||
2965 | 261 | <property name="position">1</property> | ||
2966 | 262 | </packing> | ||
2968 | 263 | </child> | 217 | </child> |
2969 | 264 | <child type="tab"> | 218 | <child type="tab"> |
2978 | 265 | <object class="GtkLabel" id="prefs-label2"> | 219 | <placeholder/> |
2971 | 266 | <property name="visible">True</property> | ||
2972 | 267 | <property name="label" translatable="yes">Storage</property> | ||
2973 | 268 | </object> | ||
2974 | 269 | <packing> | ||
2975 | 270 | <property name="position">1</property> | ||
2976 | 271 | <property name="tab_fill">False</property> | ||
2977 | 272 | </packing> | ||
2979 | 273 | </child> | 220 | </child> |
2980 | 274 | <child> | 221 | <child> |
2981 | 275 | <object class="GtkAlignment" id="prefs-alignment4"> | 222 | <object class="GtkAlignment" id="prefs-alignment4"> |
2982 | 276 | 223 | ||
2983 | === modified file 'GTG/gtk/preferences.py' | |||
2984 | --- GTG/gtk/preferences.py 2010-06-10 14:45:36 +0000 | |||
2985 | +++ GTG/gtk/preferences.py 2010-06-23 01:19:23 +0000 | |||
2986 | @@ -270,7 +270,7 @@ | |||
2987 | 270 | self.config["plugins"]["enabled"] = \ | 270 | self.config["plugins"]["enabled"] = \ |
2988 | 271 | self.pengine.enabled_plugins().keys() | 271 | self.pengine.enabled_plugins().keys() |
2989 | 272 | 272 | ||
2991 | 273 | self.config_obj.save_config() | 273 | self.config_obj.save() |
2992 | 274 | 274 | ||
2993 | 275 | self.dialog.hide() | 275 | self.dialog.hide() |
2994 | 276 | return True | 276 | return True |
2995 | 277 | 277 | ||
2996 | === modified file 'GTG/tests/__init__.py' | |||
2997 | --- GTG/tests/__init__.py 2010-06-22 09:43:55 +0000 | |||
2998 | +++ GTG/tests/__init__.py 2010-06-23 01:19:23 +0000 | |||
2999 | @@ -19,22 +19,31 @@ | |||
3000 | 19 | 19 | ||
3001 | 20 | """Unit tests for GTG.""" | 20 | """Unit tests for GTG.""" |
3002 | 21 | 21 | ||
3003 | 22 | from GTG.tools.testingmode import TestingMode | ||
3004 | 23 | TestingMode().set_testing_mode(True) | ||
3005 | 24 | |||
3006 | 25 | |||
3007 | 22 | import unittest | 26 | import unittest |
3008 | 23 | 27 | ||
3009 | 28 | |||
3010 | 24 | from GTG.tests import ( | 29 | from GTG.tests import ( |
3011 | 25 | test_tagstore, | 30 | test_tagstore, |
3012 | 26 | test_taskviewserial, | 31 | test_taskviewserial, |
3013 | 27 | test_tree, | 32 | test_tree, |
3014 | 28 | test_apidocs, | 33 | test_apidocs, |
3015 | 34 | test_backends, | ||
3016 | 35 | test_datastore, | ||
3017 | 29 | test_filteredtree, | 36 | test_filteredtree, |
3018 | 30 | ) | 37 | ) |
3019 | 31 | 38 | ||
3020 | 32 | |||
3021 | 33 | def test_suite(): | 39 | def test_suite(): |
3022 | 34 | return unittest.TestSuite([ | 40 | return unittest.TestSuite([ |
3023 | 35 | test_tagstore.test_suite(), | 41 | test_tagstore.test_suite(), |
3024 | 36 | test_taskviewserial.test_suite(), | 42 | test_taskviewserial.test_suite(), |
3025 | 37 | test_tree.test_suite(), | 43 | test_tree.test_suite(), |
3026 | 38 | test_apidocs.test_suite(), | 44 | test_apidocs.test_suite(), |
3027 | 45 | test_backends.test_suite(), | ||
3028 | 46 | test_datastore.test_suite(), | ||
3029 | 39 | test_filteredtree.test_suite(), | 47 | test_filteredtree.test_suite(), |
3030 | 40 | ]) | 48 | ]) |
3031 | 49 | |||
3032 | 41 | 50 | ||
3033 | === modified file 'GTG/tests/test_apidocs.py' | |||
3034 | --- GTG/tests/test_apidocs.py 2010-05-29 14:39:28 +0000 | |||
3035 | +++ GTG/tests/test_apidocs.py 2010-06-23 01:19:23 +0000 | |||
3036 | @@ -27,6 +27,8 @@ | |||
3037 | 27 | import shutil | 27 | import shutil |
3038 | 28 | import uuid | 28 | import uuid |
3039 | 29 | 29 | ||
3040 | 30 | from GTG.core import CoreConfig | ||
3041 | 31 | |||
3042 | 30 | 32 | ||
3043 | 31 | 33 | ||
3044 | 32 | class TestApiDocs(unittest.TestCase): | 34 | class TestApiDocs(unittest.TestCase): |
3045 | @@ -50,4 +52,6 @@ | |||
3046 | 50 | shutil.rmtree(api_dir) | 52 | shutil.rmtree(api_dir) |
3047 | 51 | 53 | ||
3048 | 52 | def test_suite(): | 54 | def test_suite(): |
3049 | 55 | CoreConfig().set_data_dir("./test_data") | ||
3050 | 56 | CoreConfig().set_conf_dir("./test_data") | ||
3051 | 53 | return unittest.TestLoader().loadTestsFromTestCase(TestApiDocs) | 57 | return unittest.TestLoader().loadTestsFromTestCase(TestApiDocs) |
3052 | 54 | 58 | ||
3053 | === added file 'GTG/tests/test_backends.py' | |||
3054 | --- GTG/tests/test_backends.py 1970-01-01 00:00:00 +0000 | |||
3055 | +++ GTG/tests/test_backends.py 2010-06-23 01:19:23 +0000 | |||
3056 | @@ -0,0 +1,191 @@ | |||
3057 | 1 | # -*- coding: utf-8 -*- | ||
3058 | 2 | # ----------------------------------------------------------------------------- | ||
3059 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
3060 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
3061 | 5 | # | ||
3062 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
3063 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
3064 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
3065 | 9 | # version. | ||
3066 | 10 | # | ||
3067 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
3068 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
3069 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
3070 | 14 | # details. | ||
3071 | 15 | # | ||
3072 | 16 | # You should have received a copy of the GNU General Public License along with | ||
3073 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
3074 | 18 | # ----------------------------------------------------------------------------- | ||
3075 | 19 | |||
3076 | 20 | """Tests for GTG backends. | ||
3077 | 21 | |||
3078 | 22 | Some of these tests will generate files in | ||
3079 | 23 | xdg.BaseDirectory.xdg_data_home/gtg directory. | ||
3080 | 24 | """ | ||
3081 | 25 | |||
3082 | 26 | # Standard imports | ||
3083 | 27 | import unittest | ||
3084 | 28 | import os | ||
3085 | 29 | import xdg | ||
3086 | 30 | |||
3087 | 31 | # GTG imports | ||
3088 | 32 | from GTG.backends import backend_localfile as localfile | ||
3089 | 33 | from GTG.core import datastore | ||
3090 | 34 | from GTG.tools import cleanxml | ||
3091 | 35 | from GTG.core import CoreConfig | ||
3092 | 36 | |||
3093 | 37 | |||
3094 | 38 | class GtgBackendsUniTests(unittest.TestCase): | ||
3095 | 39 | """Tests for GTG backends.""" | ||
3096 | 40 | |||
3097 | 41 | def __init__(self, test): | ||
3098 | 42 | unittest.TestCase.__init__(self, test) | ||
3099 | 43 | self.taskfile = '' | ||
3100 | 44 | self.datafile = '' | ||
3101 | 45 | self.taskpath = '' | ||
3102 | 46 | self.datapath = '' | ||
3103 | 47 | |||
3104 | 48 | def SetUp(self): | ||
3105 | 49 | CoreConfig().set_data_dir("./test_data") | ||
3106 | 50 | CoreConfig().set_conf_dir("./test_data") | ||
3107 | 51 | |||
3108 | 52 | def test_localfile_get_name(self): | ||
3109 | 53 | """Tests for localfile/get_name function : | ||
3110 | 54 | - a string is expected. | ||
3111 | 55 | """ | ||
3112 | 56 | res = localfile.Backend.get_name() | ||
3113 | 57 | expectedres = "backend_localfile" | ||
3114 | 58 | self.assertEqual(res, expectedres) | ||
3115 | 59 | |||
3116 | 60 | def test_localfile_get_description(self): | ||
3117 | 61 | """Tests for localfile/get_description function : | ||
3118 | 62 | - a string is expected. | ||
3119 | 63 | """ | ||
3120 | 64 | res = localfile.Backend.get_description() | ||
3121 | 65 | expectedres = "Your tasks are saved" | ||
3122 | 66 | self.assertEqual(res[:len(expectedres)], expectedres) | ||
3123 | 67 | |||
3124 | 68 | |||
3125 | 69 | def test_localfile_get_static_parameters(self): | ||
3126 | 70 | """Tests for localfile/get_static_parameters function: | ||
3127 | 71 | - a string is expected. | ||
3128 | 72 | """ | ||
3129 | 73 | res = localfile.Backend.get_static_parameters() | ||
3130 | 74 | self.assertEqual(res['path']['type'], "string") | ||
3131 | 75 | |||
3132 | 76 | def test_localfile_get_type(self): | ||
3133 | 77 | """Tests for localfile/get_type function: | ||
3134 | 78 | - a string is expected. | ||
3135 | 79 | """ | ||
3136 | 80 | res = localfile.Backend.get_type() | ||
3137 | 81 | expectedres = "readwrite" | ||
3138 | 82 | self.assertEqual(res, expectedres) | ||
3139 | 83 | |||
3140 | 84 | |||
3141 | 85 | def test_localfile_backend_method3(self): | ||
3142 | 86 | """Tests for localfile/Backend/remove_task method: | ||
3143 | 87 | - parse task file to check if task has been removed. | ||
3144 | 88 | """ | ||
3145 | 89 | self.create_test_environment() | ||
3146 | 90 | doc, configxml = cleanxml.openxmlfile(self.datapath, 'config') | ||
3147 | 91 | xmlproject = doc.getElementsByTagName('backend') | ||
3148 | 92 | for domobj in xmlproject: | ||
3149 | 93 | dic = {} | ||
3150 | 94 | if domobj.hasAttribute("module"): | ||
3151 | 95 | dic["module"] = str(domobj.getAttribute("module")) | ||
3152 | 96 | dic["pid"] = str(domobj.getAttribute("pid")) | ||
3153 | 97 | dic["xmlobject"] = domobj | ||
3154 | 98 | dic["Enabled"] = True | ||
3155 | 99 | dic["path"] = self.taskpath | ||
3156 | 100 | beobj = localfile.Backend(dic) | ||
3157 | 101 | expectedres = True | ||
3158 | 102 | beobj.remove_task("0@1") | ||
3159 | 103 | beobj.quit() | ||
3160 | 104 | dataline = open(self.taskpath, 'r').read() | ||
3161 | 105 | print dataline | ||
3162 | 106 | if "0@1" in dataline: | ||
3163 | 107 | res = False | ||
3164 | 108 | else: | ||
3165 | 109 | res = True | ||
3166 | 110 | expectedres = True | ||
3167 | 111 | self.assertEqual(res, expectedres) | ||
3168 | 112 | |||
3169 | 113 | # def test_localfile_backend_method4(self): | ||
3170 | 114 | # """Tests for localfile/Backend/get_task method: | ||
3171 | 115 | # - Compares task titles to check if method works. | ||
3172 | 116 | # """ | ||
3173 | 117 | # self.create_test_environment() | ||
3174 | 118 | # doc, configxml = cleanxml.openxmlfile(self.datapath, 'config') | ||
3175 | 119 | # xmlproject = doc.getElementsByTagName('backend') | ||
3176 | 120 | # for domobj in xmlproject: | ||
3177 | 121 | # dic = {} | ||
3178 | 122 | # if domobj.hasAttribute("module"): | ||
3179 | 123 | # dic["module"] = str(domobj.getAttribute("module")) | ||
3180 | 124 | # dic["pid"] = str(domobj.getAttribute("pid")) | ||
3181 | 125 | # dic["xmlobject"] = domobj | ||
3182 | 126 | # dic["filename"] = self.taskfile | ||
3183 | 127 | # beobj = localfile.Backend(dic) | ||
3184 | 128 | # dstore = datastore.DataStore() | ||
3185 | 129 | # newtask = dstore.new_task(tid="0@2", pid="1", newtask=True) | ||
3186 | 130 | # beobj.get_task(newtask, "0@1") | ||
3187 | 131 | # self.assertEqual(newtask.get_title(), u"Ceci est un test") | ||
3188 | 132 | |||
3189 | 133 | # def test_localfile_backend_method5(self): | ||
3190 | 134 | # """Tests for localfile/Backend/set_task method: | ||
3191 | 135 | # - parses task file to check if new task has been stored. | ||
3192 | 136 | # """ | ||
3193 | 137 | # self.create_test_environment() | ||
3194 | 138 | # doc, configxml = cleanxml.openxmlfile(self.datapath, 'config') | ||
3195 | 139 | # xmlproject = doc.getElementsByTagName('backend') | ||
3196 | 140 | # for domobj in xmlproject: | ||
3197 | 141 | # dic = {} | ||
3198 | 142 | # if domobj.hasAttribute("module"): | ||
3199 | 143 | # dic["module"] = str(domobj.getAttribute("module")) | ||
3200 | 144 | # dic["pid"] = str(domobj.getAttribute("pid")) | ||
3201 | 145 | # dic["xmlobject"] = domobj | ||
3202 | 146 | # dic["filename"] = self.taskfile | ||
3203 | 147 | # beobj = localfile.Backend(dic) | ||
3204 | 148 | # dstore = datastore.DataStore() | ||
3205 | 149 | # newtask = dstore.new_task(tid="0@2", pid="1", newtask=True) | ||
3206 | 150 | # beobj.set_task(newtask) | ||
3207 | 151 | # dataline = open(self.taskpath, 'r').read() | ||
3208 | 152 | # if "0@2" in dataline: | ||
3209 | 153 | # res = True | ||
3210 | 154 | # else: | ||
3211 | 155 | # res = False | ||
3212 | 156 | # expectedres = True | ||
3213 | 157 | # self.assertEqual(res, expectedres) | ||
3214 | 158 | |||
3215 | 159 | def create_test_environment(self): | ||
3216 | 160 | """Create the test environment""" | ||
3217 | 161 | self.taskfile = 'test.xml' | ||
3218 | 162 | self.datafile = 'projectstest.xml' | ||
3219 | 163 | tasks = [ | ||
3220 | 164 | '<?xml version="1.0" ?>\n', | ||
3221 | 165 | '<project>\n', | ||
3222 | 166 | '\t<task id="0@1" status="Active" tags="">\n', | ||
3223 | 167 | '\t\t<title>\n', | ||
3224 | 168 | '\t\t\tCeci est un test\n', | ||
3225 | 169 | '\t\t</title>\n', | ||
3226 | 170 | '\t</task>\n', | ||
3227 | 171 | '</project>\n', | ||
3228 | 172 | ] | ||
3229 | 173 | data = [ | ||
3230 | 174 | '<?xml version="1.0" ?>\n', | ||
3231 | 175 | '<config>\n', | ||
3232 | 176 | '\t<backend filename="test.xml" module="localfile" pid="1"/>\n', | ||
3233 | 177 | '</config>\n', | ||
3234 | 178 | ] | ||
3235 | 179 | self.testdir = os.path.join(xdg.BaseDirectory.xdg_data_home, 'gtg') | ||
3236 | 180 | if not os.path.exists(self.testdir): | ||
3237 | 181 | os.makedirs(self.testdir) | ||
3238 | 182 | self.taskpath = os.path.join(self.testdir, self.taskfile) | ||
3239 | 183 | self.datapath = os.path.join(self.testdir, self.datafile) | ||
3240 | 184 | open(self.taskpath, 'w').writelines(tasks) | ||
3241 | 185 | open(self.datapath, 'w').writelines(data) | ||
3242 | 186 | |||
3243 | 187 | |||
3244 | 188 | def test_suite(): | ||
3245 | 189 | CoreConfig().set_data_dir("./test_data") | ||
3246 | 190 | CoreConfig().set_conf_dir("./test_data") | ||
3247 | 191 | return unittest.TestLoader().loadTestsFromName(__name__) | ||
3248 | 0 | 192 | ||
3249 | === added file 'GTG/tests/test_datastore.py' | |||
3250 | --- GTG/tests/test_datastore.py 1970-01-01 00:00:00 +0000 | |||
3251 | +++ GTG/tests/test_datastore.py 2010-06-23 01:19:23 +0000 | |||
3252 | @@ -0,0 +1,360 @@ | |||
3253 | 1 | # -*- coding: utf-8 -*- | ||
3254 | 2 | # ----------------------------------------------------------------------------- | ||
3255 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
3256 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
3257 | 5 | # | ||
3258 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
3259 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
3260 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
3261 | 9 | # version. | ||
3262 | 10 | # | ||
3263 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
3264 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
3265 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
3266 | 14 | # details. | ||
3267 | 15 | # | ||
3268 | 16 | # You should have received a copy of the GNU General Public License along with | ||
3269 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
3270 | 18 | # ----------------------------------------------------------------------------- | ||
3271 | 19 | |||
3272 | 20 | ''' | ||
3273 | 21 | Tests for the datastore | ||
3274 | 22 | ''' | ||
3275 | 23 | |||
3276 | 24 | import unittest | ||
3277 | 25 | import uuid | ||
3278 | 26 | import random | ||
3279 | 27 | import time | ||
3280 | 28 | |||
3281 | 29 | import GTG | ||
3282 | 30 | from GTG.core.datastore import DataStore | ||
3283 | 31 | from GTG.backends.genericbackend import GenericBackend | ||
3284 | 32 | from GTG.core import CoreConfig | ||
3285 | 33 | |||
3286 | 34 | |||
3287 | 35 | |||
3288 | 36 | class TestDatastore(unittest.TestCase): | ||
3289 | 37 | ''' | ||
3290 | 38 | Tests for the DataStore object. | ||
3291 | 39 | ''' | ||
3292 | 40 | |||
3293 | 41 | |||
3294 | 42 | def setUp(self): | ||
3295 | 43 | ''' | ||
3296 | 44 | Creates the environment for the tests | ||
3297 | 45 | @returns None | ||
3298 | 46 | ''' | ||
3299 | 47 | self.datastore = DataStore() | ||
3300 | 48 | self.requester = self.datastore.get_requester() | ||
3301 | 49 | |||
3302 | 50 | def test_task_factory(self): | ||
3303 | 51 | ''' | ||
3304 | 52 | Test for the task_factory function | ||
3305 | 53 | ''' | ||
3306 | 54 | #generate a Task with a random id | ||
3307 | 55 | tid = str(uuid.uuid4()) | ||
3308 | 56 | task = self.datastore.task_factory(tid, newtask = True) | ||
3309 | 57 | self.assertTrue(isinstance(task, GTG.core.task.Task)) | ||
3310 | 58 | self.assertEqual(task.get_id(), tid) | ||
3311 | 59 | self.assertEqual(task.is_new(), True) | ||
3312 | 60 | tid = str(uuid.uuid4()) | ||
3313 | 61 | task = self.datastore.task_factory(tid, newtask = False) | ||
3314 | 62 | self.assertEqual(task.is_new(), False) | ||
3315 | 63 | |||
3316 | 64 | def test_new_task_and_has_task(self): | ||
3317 | 65 | ''' | ||
3318 | 66 | Tests the new_task function | ||
3319 | 67 | ''' | ||
3320 | 68 | task = self.datastore.new_task() | ||
3321 | 69 | tid = task.get_id() | ||
3322 | 70 | self.assertTrue(isinstance(tid, str)) | ||
3323 | 71 | self.assertTrue(tid != '') | ||
3324 | 72 | self.assertTrue(task.is_new()) | ||
3325 | 73 | self.assertTrue(self.datastore.has_task(tid)) | ||
3326 | 74 | self.assertTrue(len(self.datastore.get_all_tasks()) == 1) | ||
3327 | 75 | |||
3328 | 76 | def test_get_all_tasks(self): | ||
3329 | 77 | ''' | ||
3330 | 78 | Tests the get_all_tasks function | ||
3331 | 79 | ''' | ||
3332 | 80 | task_ids = [] | ||
3333 | 81 | for i in xrange(1, 10): | ||
3334 | 82 | task = self.datastore.new_task() | ||
3335 | 83 | task_ids.append(task.get_id()) | ||
3336 | 84 | return_list =self.datastore.get_all_tasks() | ||
3337 | 85 | self.assertEqual(len(return_list), i) | ||
3338 | 86 | task_ids.sort() | ||
3339 | 87 | return_list.sort() | ||
3340 | 88 | self.assertEqual(task_ids, return_list) | ||
3341 | 89 | |||
3342 | 90 | def test_get_task(self): | ||
3343 | 91 | ''' | ||
3344 | 92 | Tests the get_task function | ||
3345 | 93 | ''' | ||
3346 | 94 | self.assertEqual(self.datastore.get_task(str(uuid.uuid4())), None) | ||
3347 | 95 | task = self.datastore.new_task() | ||
3348 | 96 | self.assertTrue(isinstance(self.datastore.get_task(task.get_id()), | ||
3349 | 97 | GTG.core.task.Task)) | ||
3350 | 98 | self.assertEqual(self.datastore.get_task(task.get_id()), task) | ||
3351 | 99 | |||
3352 | 100 | |||
3353 | 101 | def test_get_tagstore(self): | ||
3354 | 102 | ''' | ||
3355 | 103 | Tests the get_tagstore function | ||
3356 | 104 | ''' | ||
3357 | 105 | tagstore = self.datastore.get_tagstore() | ||
3358 | 106 | self.assertTrue(isinstance(tagstore, GTG.core.tagstore.TagStore)) | ||
3359 | 107 | |||
3360 | 108 | def test_get_requester(self): | ||
3361 | 109 | ''' | ||
3362 | 110 | Tests the get_requester function | ||
3363 | 111 | ''' | ||
3364 | 112 | requester = self.datastore.get_requester() | ||
3365 | 113 | self.assertTrue(isinstance(requester, GTG.core.requester.Requester)) | ||
3366 | 114 | |||
3367 | 115 | def test_get_tasks_tree(self): | ||
3368 | 116 | ''' | ||
3369 | 117 | Tests the get_tasks_tree function | ||
3370 | 118 | ''' | ||
3371 | 119 | tasks_tree = self.datastore.get_tasks_tree() | ||
3372 | 120 | self.assertTrue(isinstance(tasks_tree, GTG.core.tree.Tree)) | ||
3373 | 121 | |||
3374 | 122 | def test_push_task(self): | ||
3375 | 123 | ''' | ||
3376 | 124 | Tests the push_task function | ||
3377 | 125 | ''' | ||
3378 | 126 | task_ids = [] | ||
3379 | 127 | for i in xrange(1, 10): | ||
3380 | 128 | tid = str(uuid.uuid4()) | ||
3381 | 129 | if tid not in task_ids: | ||
3382 | 130 | task_ids.append(tid) | ||
3383 | 131 | task = self.datastore.task_factory(tid) | ||
3384 | 132 | return_value1 = self.datastore.push_task(task) | ||
3385 | 133 | self.assertTrue(return_value1) | ||
3386 | 134 | #we do it twice, but it should be pushed only once if it's | ||
3387 | 135 | # working correctly (the second should be discarded) | ||
3388 | 136 | return_value2 = self.datastore.push_task(task) | ||
3389 | 137 | self.assertFalse(return_value2) | ||
3390 | 138 | stored_tasks = self.datastore.get_all_tasks() | ||
3391 | 139 | task_ids.sort() | ||
3392 | 140 | stored_tasks.sort() | ||
3393 | 141 | self.assertEqual(task_ids, stored_tasks) | ||
3394 | 142 | |||
3395 | 143 | def test_register_backend(self): | ||
3396 | 144 | ''' | ||
3397 | 145 | Tests the register_backend function. It also tests the | ||
3398 | 146 | get_all_backends and get_backend function as a side effect | ||
3399 | 147 | ''' | ||
3400 | 148 | #create a simple backend dictionary | ||
3401 | 149 | backend = FakeBackend(enabled = True) | ||
3402 | 150 | tasks_in_backend_count = int(random.random() * 20) | ||
3403 | 151 | for temp in xrange(0, tasks_in_backend_count): | ||
3404 | 152 | backend.fake_add_random_task() | ||
3405 | 153 | backend_dic = {'backend': backend, 'pid': 'a'} | ||
3406 | 154 | self.datastore.register_backend(backend_dic) | ||
3407 | 155 | all_backends = self.datastore.get_all_backends(disabled = True) | ||
3408 | 156 | self.assertEqual(len(all_backends), 1) | ||
3409 | 157 | registered_backend = self.datastore.get_backend(backend.get_id()) | ||
3410 | 158 | self.assertEqual(backend.get_id(), registered_backend.get_id()) | ||
3411 | 159 | self.assertTrue(isinstance(registered_backend, \ | ||
3412 | 160 | GTG.core.datastore.TaskSource)) | ||
3413 | 161 | self.assertTrue(registered_backend.is_enabled()) | ||
3414 | 162 | self.assertEqual(registered_backend.fake_get_initialized_count(), 1) | ||
3415 | 163 | #we give some time for the backend to push all its tasks | ||
3416 | 164 | time.sleep(1) | ||
3417 | 165 | self.assertEqual(len(self.datastore.get_all_tasks()), \ | ||
3418 | 166 | tasks_in_backend_count) | ||
3419 | 167 | |||
3420 | 168 | #same test, disabled backend | ||
3421 | 169 | backend = FakeBackend(enabled = False) | ||
3422 | 170 | for temp in xrange(1, int(random.random() * 20)): | ||
3423 | 171 | backend.fake_add_random_task() | ||
3424 | 172 | backend_dic = {'backend': backend, 'pid':'b'} | ||
3425 | 173 | self.datastore.register_backend(backend_dic) | ||
3426 | 174 | all_backends = self.datastore.get_all_backends(disabled = True) | ||
3427 | 175 | self.assertEqual(len(all_backends), 2) | ||
3428 | 176 | all_backends = self.datastore.get_all_backends(disabled = False) | ||
3429 | 177 | self.assertEqual(len(all_backends), 1) | ||
3430 | 178 | registered_backend = self.datastore.get_backend(backend.get_id()) | ||
3431 | 179 | self.assertEqual(backend.get_id(), registered_backend.get_id()) | ||
3432 | 180 | self.assertTrue(isinstance(registered_backend, \ | ||
3433 | 181 | GTG.core.datastore.TaskSource)) | ||
3434 | 182 | self.assertFalse(registered_backend.is_enabled()) | ||
3435 | 183 | self.assertEqual(registered_backend.fake_get_initialized_count(), 0) | ||
3436 | 184 | #we give some time for the backend to push all its tasks (is | ||
3437 | 185 | #shouldn't, since it's disabled, but we give time anyway | ||
3438 | 186 | time.sleep(1) | ||
3439 | 187 | self.assertEqual(len(self.datastore.get_all_tasks()), \ | ||
3440 | 188 | tasks_in_backend_count) | ||
3441 | 189 | |||
3442 | 190 | def test_set_backend_enabled(self): | ||
3443 | 191 | ''' | ||
3444 | 192 | Tests the set_backend_enabled function | ||
3445 | 193 | ''' | ||
3446 | 194 | enabled_backend = FakeBackend(enabled = True) | ||
3447 | 195 | disabled_backend = FakeBackend(enabled = False) | ||
3448 | 196 | self.datastore.register_backend({'backend': enabled_backend, \ | ||
3449 | 197 | 'pid': str(uuid.uuid4()), \ | ||
3450 | 198 | GenericBackend.KEY_DEFAULT_BACKEND: False}) | ||
3451 | 199 | self.datastore.register_backend({'backend': disabled_backend,\ | ||
3452 | 200 | 'pid': str(uuid.uuid4()), \ | ||
3453 | 201 | GenericBackend.KEY_DEFAULT_BACKEND: False}) | ||
3454 | 202 | #enabling an enabled backend | ||
3455 | 203 | self.datastore.set_backend_enabled(enabled_backend.get_id(), True) | ||
3456 | 204 | self.assertEqual(enabled_backend.fake_get_initialized_count(), 1) | ||
3457 | 205 | self.assertTrue(enabled_backend.is_enabled()) | ||
3458 | 206 | #disabling a disabled backend | ||
3459 | 207 | self.datastore.set_backend_enabled(disabled_backend.get_id(), False) | ||
3460 | 208 | self.assertEqual(disabled_backend.fake_get_initialized_count(), 0) | ||
3461 | 209 | self.assertFalse(disabled_backend.is_enabled()) | ||
3462 | 210 | #disabling an enabled backend | ||
3463 | 211 | self.datastore.set_backend_enabled(enabled_backend.get_id(), False) | ||
3464 | 212 | self.assertEqual(enabled_backend.fake_get_initialized_count(), 1) | ||
3465 | 213 | self.assertFalse(enabled_backend.is_enabled()) | ||
3466 | 214 | time.sleep(1) | ||
3467 | 215 | # #enabling a disabled backend | ||
3468 | 216 | # self.datastore.set_backend_enabled(disabled_backend.get_id(), True) | ||
3469 | 217 | # self.assertEqual(disabled_backend.fake_get_initialized_count(), 1) | ||
3470 | 218 | # self.assertTrue(disabled_backend.is_enabled()) | ||
3471 | 219 | |||
3472 | 220 | def test_remove_backend(self): | ||
3473 | 221 | ''' | ||
3474 | 222 | Tests the remove_backend function | ||
3475 | 223 | ''' | ||
3476 | 224 | enabled_backend = FakeBackend(enabled = True) | ||
3477 | 225 | disabled_backend = FakeBackend(enabled = False) | ||
3478 | 226 | self.datastore.register_backend({'backend': enabled_backend, \ | ||
3479 | 227 | 'pid': str(uuid.uuid4()), \ | ||
3480 | 228 | GenericBackend.KEY_DEFAULT_BACKEND: False}) | ||
3481 | 229 | self.datastore.register_backend({'backend': disabled_backend,\ | ||
3482 | 230 | 'pid': str(uuid.uuid4()), \ | ||
3483 | 231 | GenericBackend.KEY_DEFAULT_BACKEND: False}) | ||
3484 | 232 | #removing an enabled backend | ||
3485 | 233 | self.datastore.remove_backend(enabled_backend.get_id()) | ||
3486 | 234 | self.assertFalse(enabled_backend.is_enabled()) | ||
3487 | 235 | self.assertTrue(enabled_backend.fake_is_purged()) | ||
3488 | 236 | self.assertEqual( \ | ||
3489 | 237 | len(self.datastore.get_all_backends(disabled = True)), 1) | ||
3490 | 238 | #removing a disabled backend | ||
3491 | 239 | self.datastore.remove_backend(disabled_backend.get_id()) | ||
3492 | 240 | self.assertFalse(disabled_backend.is_enabled()) | ||
3493 | 241 | self.assertTrue(disabled_backend.fake_is_purged()) | ||
3494 | 242 | self.assertEqual( \ | ||
3495 | 243 | len(self.datastore.get_all_backends(disabled = True)), 0) | ||
3496 | 244 | |||
3497 | 245 | def test_flush_all_tasks(self): | ||
3498 | 246 | ''' | ||
3499 | 247 | Tests the flush_all_tasks function | ||
3500 | 248 | ''' | ||
3501 | 249 | #we add some tasks in the datastore | ||
3502 | 250 | tasks_in_datastore_count = 10 #int(random.random() * 20) | ||
3503 | 251 | for temp in xrange(0, tasks_in_datastore_count): | ||
3504 | 252 | self.datastore.new_task() | ||
3505 | 253 | datastore_stored_tids = self.datastore.get_all_tasks() | ||
3506 | 254 | self.assertEqual(tasks_in_datastore_count, len(datastore_stored_tids)) | ||
3507 | 255 | |||
3508 | 256 | #we enable a backend | ||
3509 | 257 | backend = FakeBackend(enabled = True) | ||
3510 | 258 | self.datastore.register_backend({'backend': backend, 'pid': 'a'}) | ||
3511 | 259 | #we wait for the signal storm to wear off | ||
3512 | 260 | time.sleep(5) | ||
3513 | 261 | #we sync | ||
3514 | 262 | self.datastore.get_backend(backend.get_id()).sync() | ||
3515 | 263 | #and we inject task in the backend | ||
3516 | 264 | tasks_in_backend_count = 5 #int(random.random() * 20) | ||
3517 | 265 | for temp in xrange(0, tasks_in_backend_count): | ||
3518 | 266 | backend.fake_add_random_task() | ||
3519 | 267 | backend_stored_tids = backend.fake_get_task_ids() | ||
3520 | 268 | self.assertEqual(tasks_in_backend_count, len(backend_stored_tids)) | ||
3521 | 269 | self.datastore.flush_all_tasks(backend.get_id()) | ||
3522 | 270 | #we wait for the signal storm to wear off | ||
3523 | 271 | time.sleep(2) | ||
3524 | 272 | #we sync | ||
3525 | 273 | self.datastore.get_backend(backend.get_id()).sync() | ||
3526 | 274 | all_tasks_count = tasks_in_backend_count + tasks_in_datastore_count | ||
3527 | 275 | new_datastore_stored_tids = self.datastore.get_all_tasks() | ||
3528 | 276 | new_backend_stored_tids = backend.fake_get_task_ids() | ||
3529 | 277 | self.assertEqual(len(new_backend_stored_tids), all_tasks_count) | ||
3530 | 278 | self.assertEqual(len(new_datastore_stored_tids), all_tasks_count) | ||
3531 | 279 | new_datastore_stored_tids.sort() | ||
3532 | 280 | new_backend_stored_tids.sort() | ||
3533 | 281 | self.assertEqual(new_backend_stored_tids, new_datastore_stored_tids) | ||
3534 | 282 | |||
3535 | 283 | |||
3536 | 284 | |||
3537 | 285 | def test_suite(): | ||
3538 | 286 | CoreConfig().set_data_dir("./test_data") | ||
3539 | 287 | CoreConfig().set_conf_dir("./test_data") | ||
3540 | 288 | return unittest.TestLoader().loadTestsFromTestCase(TestDatastore) | ||
3541 | 289 | |||
3542 | 290 | |||
3543 | 291 | |||
3544 | 292 | class FakeBackend(unittest.TestCase): | ||
3545 | 293 | ''' | ||
3546 | 294 | Mimics the behavior of a simple backend. Just used for testing | ||
3547 | 295 | ''' | ||
3548 | 296 | |||
3549 | 297 | def __init__(self, enabled = True): | ||
3550 | 298 | self.enabled = enabled | ||
3551 | 299 | self.initialized_count = 0 | ||
3552 | 300 | self.tasks_ids = [] | ||
3553 | 301 | self.backend_id = str(uuid.uuid4()) | ||
3554 | 302 | self.purged = False | ||
3555 | 303 | |||
3556 | 304 | def is_enabled(self): | ||
3557 | 305 | return self.enabled | ||
3558 | 306 | |||
3559 | 307 | def initialize(self): | ||
3560 | 308 | self.initialized_count += 1 | ||
3561 | 309 | self.enabled = True | ||
3562 | 310 | |||
3563 | 311 | def queue_set_task(self, task): | ||
3564 | 312 | self.tasks_ids.append(task.get_id()) | ||
3565 | 313 | |||
3566 | 314 | def has_task(self, task_id): | ||
3567 | 315 | return task_id in self.tasks_ids | ||
3568 | 316 | |||
3569 | 317 | def queue_remove_task(self, task_id): | ||
3570 | 318 | self.tasks_ids.remove(task_id) | ||
3571 | 319 | |||
3572 | 320 | def get_id(self): | ||
3573 | 321 | return self.backend_id | ||
3574 | 322 | |||
3575 | 323 | def start_get_tasks(self): | ||
3576 | 324 | for task_id in self.tasks_ids: | ||
3577 | 325 | self.datastore.push_task(self.datastore.task_factory(task_id)) | ||
3578 | 326 | |||
3579 | 327 | def quit(self, disabled = False): | ||
3580 | 328 | self.enabled = not disabled | ||
3581 | 329 | |||
3582 | 330 | def purge(self): | ||
3583 | 331 | self.purged = True | ||
3584 | 332 | |||
3585 | 333 | def is_default(self): | ||
3586 | 334 | return True | ||
3587 | 335 | |||
3588 | 336 | def set_parameter(self, param_name, param_value): | ||
3589 | 337 | pass | ||
3590 | 338 | |||
3591 | 339 | def get_attached_tags(self): | ||
3592 | 340 | return [CoreConfig.ALLTASKS_TAG] | ||
3593 | 341 | |||
3594 | 342 | def register_datastore(self, datastore): | ||
3595 | 343 | self.datastore = datastore | ||
3596 | 344 | |||
3597 | 345 | ########################################################################## | ||
3598 | 346 | # The following are used just for testing, they're not present inside a | ||
3599 | 347 | # normal backend | ||
3600 | 348 | ########################################################################## | ||
3601 | 349 | |||
3602 | 350 | def fake_get_initialized_count(self): | ||
3603 | 351 | return self.initialized_count | ||
3604 | 352 | |||
3605 | 353 | def fake_get_task_ids(self): | ||
3606 | 354 | return self.tasks_ids | ||
3607 | 355 | |||
3608 | 356 | def fake_add_random_task(self): | ||
3609 | 357 | self.tasks_ids.append(str(uuid.uuid4())) | ||
3610 | 358 | |||
3611 | 359 | def fake_is_purged(self): | ||
3612 | 360 | return self.purged | ||
3613 | 0 | 361 | ||
3614 | === modified file 'GTG/tests/test_tagstore.py' | |||
3615 | --- GTG/tests/test_tagstore.py 2010-05-29 12:50:20 +0000 | |||
3616 | +++ GTG/tests/test_tagstore.py 2010-06-23 01:19:23 +0000 | |||
3617 | @@ -23,6 +23,7 @@ | |||
3618 | 23 | 23 | ||
3619 | 24 | from GTG.core.tagstore import Tag | 24 | from GTG.core.tagstore import Tag |
3620 | 25 | from GTG.core.datastore import DataStore | 25 | from GTG.core.datastore import DataStore |
3621 | 26 | from GTG.core import CoreConfig | ||
3622 | 26 | 27 | ||
3623 | 27 | 28 | ||
3624 | 28 | 29 | ||
3625 | @@ -117,4 +118,6 @@ | |||
3626 | 117 | self.assertEqual(0, len(save_calls)) | 118 | self.assertEqual(0, len(save_calls)) |
3627 | 118 | 119 | ||
3628 | 119 | def test_suite(): | 120 | def test_suite(): |
3629 | 121 | CoreConfig().set_data_dir("./test_data") | ||
3630 | 122 | CoreConfig().set_conf_dir("./test_data") | ||
3631 | 120 | return unittest.TestLoader().loadTestsFromTestCase(TestTag) | 123 | return unittest.TestLoader().loadTestsFromTestCase(TestTag) |
3632 | 121 | 124 | ||
3633 | === modified file 'GTG/tests/test_taskviewserial.py' | |||
3634 | --- GTG/tests/test_taskviewserial.py 2010-06-11 14:30:12 +0000 | |||
3635 | +++ GTG/tests/test_taskviewserial.py 2010-06-23 01:19:23 +0000 | |||
3636 | @@ -27,6 +27,7 @@ | |||
3637 | 27 | import unittest | 27 | import unittest |
3638 | 28 | 28 | ||
3639 | 29 | from GTG.gtk.editor import taskviewserial | 29 | from GTG.gtk.editor import taskviewserial |
3640 | 30 | from GTG.core import CoreConfig | ||
3641 | 30 | 31 | ||
3642 | 31 | class GtgBackendsUniTests(unittest.TestCase): | 32 | class GtgBackendsUniTests(unittest.TestCase): |
3643 | 32 | """Tests for GTG backends.""" | 33 | """Tests for GTG backends.""" |
3644 | @@ -47,4 +48,6 @@ | |||
3645 | 47 | 48 | ||
3646 | 48 | 49 | ||
3647 | 49 | def test_suite(): | 50 | def test_suite(): |
3648 | 51 | CoreConfig().set_data_dir("./test_data") | ||
3649 | 52 | CoreConfig().set_conf_dir("./test_data") | ||
3650 | 50 | return unittest.TestLoader().loadTestsFromName(__name__) | 53 | return unittest.TestLoader().loadTestsFromName(__name__) |
3651 | 51 | 54 | ||
3652 | === modified file 'GTG/tests/test_tree.py' | |||
3653 | --- GTG/tests/test_tree.py 2010-02-21 19:21:18 +0000 | |||
3654 | +++ GTG/tests/test_tree.py 2010-06-23 01:19:23 +0000 | |||
3655 | @@ -22,6 +22,9 @@ | |||
3656 | 22 | import unittest | 22 | import unittest |
3657 | 23 | 23 | ||
3658 | 24 | from GTG.core.tree import Tree,TreeNode | 24 | from GTG.core.tree import Tree,TreeNode |
3659 | 25 | from GTG.core import CoreConfig | ||
3660 | 26 | |||
3661 | 27 | |||
3662 | 25 | 28 | ||
3663 | 26 | class TestTree(unittest.TestCase): | 29 | class TestTree(unittest.TestCase): |
3664 | 27 | """Tests for `Tree`.""" | 30 | """Tests for `Tree`.""" |
3665 | @@ -119,4 +122,6 @@ | |||
3666 | 119 | 122 | ||
3667 | 120 | 123 | ||
3668 | 121 | def test_suite(): | 124 | def test_suite(): |
3669 | 125 | CoreConfig().set_data_dir("./test_data") | ||
3670 | 126 | CoreConfig().set_conf_dir("./test_data") | ||
3671 | 122 | return unittest.TestLoader().loadTestsFromName(__name__) | 127 | return unittest.TestLoader().loadTestsFromName(__name__) |
3672 | 123 | 128 | ||
3673 | === added file 'GTG/tools/borg.py' | |||
3674 | --- GTG/tools/borg.py 1970-01-01 00:00:00 +0000 | |||
3675 | +++ GTG/tools/borg.py 2010-06-23 01:19:23 +0000 | |||
3676 | @@ -0,0 +1,33 @@ | |||
3677 | 1 | # -*- coding: utf-8 -*- | ||
3678 | 2 | # ----------------------------------------------------------------------------- | ||
3679 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
3680 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
3681 | 5 | # | ||
3682 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
3683 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
3684 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
3685 | 9 | # version. | ||
3686 | 10 | # | ||
3687 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
3688 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
3689 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
3690 | 14 | # details. | ||
3691 | 15 | # | ||
3692 | 16 | # You should have received a copy of the GNU General Public License along with | ||
3693 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
3694 | 18 | # ----------------------------------------------------------------------------- | ||
3695 | 19 | |||
3696 | 20 | |||
3697 | 21 | |||
3698 | 22 | class Borg(object): | ||
3699 | 23 | """ | ||
3700 | 24 | This pattern ensures that all instances of a particular class share | ||
3701 | 25 | the same state (just inherit this class to have it working) | ||
3702 | 26 | """ | ||
3703 | 27 | |||
3704 | 28 | _borg_state = {} | ||
3705 | 29 | |||
3706 | 30 | def __init__(self): | ||
3707 | 31 | self.__dict__ = self._borg_state | ||
3708 | 32 | |||
3709 | 33 | |||
3710 | 0 | 34 | ||
3711 | === added file 'GTG/tools/keyring.py' | |||
3712 | --- GTG/tools/keyring.py 1970-01-01 00:00:00 +0000 | |||
3713 | +++ GTG/tools/keyring.py 2010-06-23 01:19:23 +0000 | |||
3714 | @@ -0,0 +1,48 @@ | |||
3715 | 1 | # -*- coding: utf-8 -*- | ||
3716 | 2 | # ----------------------------------------------------------------------------- | ||
3717 | 3 | # Gettings Things Gnome! - a personal organizer for the GNOME desktop | ||
3718 | 4 | # Copyright (c) 2008-2009 - Lionel Dricot & Bertrand Rousseau | ||
3719 | 5 | # | ||
3720 | 6 | # This program is free software: you can redistribute it and/or modify it under | ||
3721 | 7 | # the terms of the GNU General Public License as published by the Free Software | ||
3722 | 8 | # Foundation, either version 3 of the License, or (at your option) any later | ||
3723 | 9 | # version. | ||
3724 | 10 | # | ||
3725 | 11 | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
3726 | 12 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
3727 | 13 | # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more | ||
3728 | 14 | # details. | ||
3729 | 15 | # | ||
3730 | 16 | # You should have received a copy of the GNU General Public License along with | ||
3731 | 17 | # this program. If not, see <http://www.gnu.org/licenses/>. | ||
3732 | 18 | # ----------------------------------------------------------------------------- | ||
3733 | 19 | |||
3734 | 20 | import gnomekeyring | ||
3735 | 21 | |||
3736 | 22 | from GTG.tools.borg import Borg | ||
3737 | 23 | |||
3738 | 24 | |||
3739 | 25 | |||
3740 | 26 | class Keyring(Borg): | ||
3741 | 27 | |||
3742 | 28 | |||
3743 | 29 | def __init__(self): | ||
3744 | 30 | super(Keyring, self).__init__() | ||
3745 | 31 | if not hasattr(self, "keyring"): | ||
3746 | 32 | self.keyring = gnomekeyring.get_default_keyring_sync() | ||
3747 | 33 | |||
3748 | 34 | def set_password(self, name, password, userid = ""): | ||
3749 | 35 | return gnomekeyring.item_create_sync( | ||
3750 | 36 | self.keyring, | ||
3751 | 37 | gnomekeyring.ITEM_GENERIC_SECRET, | ||
3752 | 38 | name, | ||
3753 | 39 | {"backend": name}, | ||
3754 | 40 | password, | ||
3755 | 41 | True) | ||
3756 | 42 | |||
3757 | 43 | def get_password(self, item_id): | ||
3758 | 44 | try: | ||
3759 | 45 | item_info = gnomekeyring.item_get_info_sync(self.keyring, item_id) | ||
3760 | 46 | return item_info.get_secret() | ||
3761 | 47 | except (gnomekeyring.DeniedError, gnomekeyring.NoMatchError): | ||
3762 | 48 | return "" | ||
3763 | 0 | 49 | ||
3764 | === modified file 'GTG/tools/logger.py' | |||
3765 | --- GTG/tools/logger.py 2010-03-02 06:32:31 +0000 | |||
3766 | +++ GTG/tools/logger.py 2010-06-23 01:19:23 +0000 | |||
3767 | @@ -41,6 +41,7 @@ | |||
3768 | 41 | #Shouldn't be needed, but the following line makes sure that | 41 | #Shouldn't be needed, but the following line makes sure that |
3769 | 42 | # this is a Singleton. | 42 | # this is a Singleton. |
3770 | 43 | self.__dict__['_Debug__logger'] = Debug.__logger | 43 | self.__dict__['_Debug__logger'] = Debug.__logger |
3771 | 44 | self.debugging_mode = False | ||
3772 | 44 | 45 | ||
3773 | 45 | def __init_logger(self): | 46 | def __init_logger(self): |
3774 | 46 | Debug.__logger = logging.getLogger('gtg_logger') | 47 | Debug.__logger = logging.getLogger('gtg_logger') |
3775 | @@ -60,5 +61,10 @@ | |||
3776 | 60 | """ Delegates to the real logger """ | 61 | """ Delegates to the real logger """ |
3777 | 61 | return setattr(Debug.__logger, attr, value) | 62 | return setattr(Debug.__logger, attr, value) |
3778 | 62 | 63 | ||
3779 | 64 | def set_debugging_mode(self, value): | ||
3780 | 65 | self.debugging_mode = value | ||
3781 | 66 | def is_debugging_mode(self): | ||
3782 | 67 | return self.debugging_mode | ||
3783 | 68 | |||
3784 | 63 | #The singleton itself | 69 | #The singleton itself |
3785 | 64 | Log = Debug() | 70 | Log = Debug() |
3786 | 65 | 71 | ||
3787 | === added file 'GTG/tools/synchronized.py' | |||
3788 | --- GTG/tools/synchronized.py 1970-01-01 00:00:00 +0000 | |||
3789 | +++ GTG/tools/synchronized.py 2010-06-23 01:19:23 +0000 | |||
3790 | @@ -0,0 +1,14 @@ | |||
3791 | 1 | from __future__ import with_statement | ||
3792 | 2 | from threading import Lock | ||
3793 | 3 | |||
3794 | 4 | def synchronized(fun): | ||
3795 | 5 | the_lock = Lock() | ||
3796 | 6 | |||
3797 | 7 | def fwrap(function): | ||
3798 | 8 | def newFunction(*args, **kw): | ||
3799 | 9 | with the_lock: | ||
3800 | 10 | return function(*args, **kw) | ||
3801 | 11 | |||
3802 | 12 | return newFunction | ||
3803 | 13 | |||
3804 | 14 | return fwrap(fun) | ||
3805 | 0 | 15 | ||
3806 | === modified file 'GTG/tools/taskxml.py' | |||
3807 | --- GTG/tools/taskxml.py 2010-06-18 16:36:17 +0000 | |||
3808 | +++ GTG/tools/taskxml.py 2010-06-23 01:19:23 +0000 | |||
3809 | @@ -60,7 +60,15 @@ | |||
3810 | 60 | cur_tags = xmlnode.getAttribute("tags").replace(' ','').split(",") | 60 | cur_tags = xmlnode.getAttribute("tags").replace(' ','').split(",") |
3811 | 61 | if "" in cur_tags: cur_tags.remove("") | 61 | if "" in cur_tags: cur_tags.remove("") |
3812 | 62 | for tag in cur_tags: cur_task.tag_added(saxutils.unescape(tag)) | 62 | for tag in cur_tags: cur_task.tag_added(saxutils.unescape(tag)) |
3814 | 63 | 63 | ||
3815 | 64 | #REMOTE TASK IDS | ||
3816 | 65 | remote_ids_list = xmlnode.getElementsByTagName("task-remote-ids") | ||
3817 | 66 | for remote_id in remote_ids_list: | ||
3818 | 67 | if remote_id.childNodes: | ||
3819 | 68 | node = remote_id.childNodes[0] | ||
3820 | 69 | backend_id = node.firstChild.nodeValue | ||
3821 | 70 | remote_task_id = node.childNodes[1].firstChild.nodeValue | ||
3822 | 71 | task.add_remote_id(backend_id, remote_task_id) | ||
3823 | 64 | return cur_task | 72 | return cur_task |
3824 | 65 | 73 | ||
3825 | 66 | #Task as parameter the doc where to put the XML node | 74 | #Task as parameter the doc where to put the XML node |
3826 | @@ -99,4 +107,18 @@ | |||
3827 | 99 | #t_xml.appendChild(element.firstChild) | 107 | #t_xml.appendChild(element.firstChild) |
3828 | 100 | cleanxml.addTextNode(doc,t_xml,"content",desc) | 108 | cleanxml.addTextNode(doc,t_xml,"content",desc) |
3829 | 101 | #self.__write_textnode(doc,t_xml,"content",t.get_text()) | 109 | #self.__write_textnode(doc,t_xml,"content",t.get_text()) |
3830 | 110 | |||
3831 | 111 | #REMOTE TASK IDS | ||
3832 | 112 | remote_ids_element = doc.createElement("task-remote-ids") | ||
3833 | 113 | t_xml.appendChild(remote_ids_element) | ||
3834 | 114 | remote_ids_dict = task.get_remote_ids() | ||
3835 | 115 | for backend_id, task_id in remote_ids_dict.iteritems(): | ||
3836 | 116 | backend_element = doc.createElement('backend') | ||
3837 | 117 | remote_ids_element.appendChild(backend_element) | ||
3838 | 118 | backend_element.appendChild(doc.createTextNode(backend_id)) | ||
3839 | 119 | task_element = doc.createElement('task-id') | ||
3840 | 120 | backend_element.appendChild(task_element) | ||
3841 | 121 | task_element.appendChild(doc.createTextNode(task_id)) | ||
3842 | 122 | |||
3843 | 123 | |||
3844 | 102 | return t_xml | 124 | return t_xml |
3845 | 103 | 125 | ||
3846 | === added file 'GTG/tools/testingmode.py' | |||
3847 | --- GTG/tools/testingmode.py 1970-01-01 00:00:00 +0000 | |||
3848 | +++ GTG/tools/testingmode.py 2010-06-23 01:19:23 +0000 | |||
3849 | @@ -0,0 +1,16 @@ | |||
3850 | 1 | from GTG.tools.borg import Borg | ||
3851 | 2 | |||
3852 | 3 | |||
3853 | 4 | |||
3854 | 5 | class TestingMode(Borg): | ||
3855 | 6 | |||
3856 | 7 | |||
3857 | 8 | def set_testing_mode(self, value): | ||
3858 | 9 | self._testing_mode = value | ||
3859 | 10 | |||
3860 | 11 | def get_testing_mode(self): | ||
3861 | 12 | try: | ||
3862 | 13 | return self._testing_mode | ||
3863 | 14 | except: | ||
3864 | 15 | return False | ||
3865 | 16 | |||
3866 | 0 | 17 | ||
3867 | === modified file 'Makefile' | |||
3868 | --- Makefile 2010-03-01 01:43:33 +0000 | |||
3869 | +++ Makefile 2010-06-23 01:19:23 +0000 | |||
3870 | @@ -35,7 +35,7 @@ | |||
3871 | 35 | # Check for coding standard violations & flakes. | 35 | # Check for coding standard violations & flakes. |
3872 | 36 | lint: pyflakes pep8 | 36 | lint: pyflakes pep8 |
3873 | 37 | 37 | ||
3875 | 38 | .PHONY: check lint pyflakes pep8 apidocs | 38 | .PHONY: check lint pyflakes pep8 apidocs edit-apidocs clean |
3876 | 39 | 39 | ||
3877 | 40 | #Ignore the exit code in pyflakes, so that pep8 is always run when "make lint" | 40 | #Ignore the exit code in pyflakes, so that pep8 is always run when "make lint" |
3878 | 41 | .IGNORE: pyflakes | 41 | .IGNORE: pyflakes |
3879 | 42 | 42 | ||
3880 | === modified file 'scripts/debug.sh' | |||
3881 | --- scripts/debug.sh 2010-06-21 09:44:23 +0000 | |||
3882 | +++ scripts/debug.sh 2010-06-23 01:19:23 +0000 | |||
3883 | @@ -42,6 +42,7 @@ | |||
3884 | 42 | if [ $norun -eq 0 ]; then | 42 | if [ $norun -eq 0 ]; then |
3885 | 43 | if [ $profile -eq 1 ]; then | 43 | if [ $profile -eq 1 ]; then |
3886 | 44 | python -m cProfile -o gtg.prof ./gtg | 44 | python -m cProfile -o gtg.prof ./gtg |
3887 | 45 | python ./scripts/profile_interpret.sh | ||
3888 | 45 | else | 46 | else |
3889 | 46 | ./gtg $args | 47 | ./gtg $args |
3890 | 47 | fi | 48 | fi |
3891 | 48 | 49 | ||
3892 | === added file 'scripts/profile_interpret.sh' | |||
3893 | --- scripts/profile_interpret.sh 1970-01-01 00:00:00 +0000 | |||
3894 | +++ scripts/profile_interpret.sh 2010-06-23 01:19:23 +0000 | |||
3895 | @@ -0,0 +1,4 @@ | |||
3896 | 1 | #!/usr/bin/env python | ||
3897 | 2 | import pstats | ||
3898 | 3 | p = pstats.Stats('gtg.prof') | ||
3899 | 4 | p.strip_dirs().sort_stats("cumulative").print_stats(20) |
It looks really good. There's plenty of good stuffs everywhere.
There's only one issue I want you to fix : filter( self, task, tags_to_match_set): should respect the filter template and, thus, the second parameter should be a "paramenter={}". Then, it's up to you to pass the tags [] in a dic.
def backend_
I believe that it will also allow you to get rid of the ugly "get_filter_func" that I prefer to avoid.
I trust you to fix that then, according to me, you can do the merge.