Merge lp:~lallenlowe/dexter-rolodex/split-in-twain into lp:dexter-rolodex

Proposed by Allen Lowe
Status: Merged
Merged at revision: 288
Proposed branch: lp:~lallenlowe/dexter-rolodex/split-in-twain
Merge into: lp:dexter-rolodex
Diff against target: 2697 lines (+1076/-719)
19 files modified
bin/dexter (+11/-9)
bin/dexter-server (+350/-0)
data/ui/dexter-main.ui (+35/-18)
debian/changelog (+1/-1)
dexter/SimpleGtkbuilderApp.py (+16/-14)
dexter/app.py (+183/-118)
dexter/backend/ddbus.py (+4/-106)
dexter/backend/models.py (+0/-2)
dexter/backend/serverutils.py (+173/-0)
dexter/backend/utilities.py (+112/-258)
dexter/enums.py (+1/-1)
dexter/frontend/dialogs/merge.py (+14/-8)
dexter/frontend/dialogs/new_contact.py (+88/-78)
dexter/frontend/utils.py (+15/-15)
dexter/frontend/widgets/entries.py (+5/-12)
dexter/signaltest.py (+0/-15)
org.elementary.dexterserver.service (+3/-0)
po/dexter.pot (+64/-64)
setup.py (+1/-0)
To merge this branch: bzr merge lp:~lallenlowe/dexter-rolodex/split-in-twain
Reviewer Review Type Date Requested Status
Postler Devs Pending
Review via email: mp+44303@code.launchpad.net

Description of the change

Split Dexter into 2 completely independent pieces, back-end server and GUI communicating over dbus.

To post a comment you must log in.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bin/dexter'
2--- bin/dexter 2010-12-13 20:18:13 +0000
3+++ bin/dexter 2010-12-20 23:23:53 +0000
4@@ -1,4 +1,4 @@
5-#!/usr/bin/python
6+#!/usr/bin/env python
7 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
8 ### BEGIN LICENSE
9 # Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
10@@ -20,6 +20,7 @@
11 from decimal import Decimal
12
13 import logging
14+import glib
15 import optparse
16 import os
17 import dbus
18@@ -48,9 +49,6 @@
19 parser.add_option(
20 "-n", "--newcontact", action="store_true", dest="newcontact",
21 help=_("Only show the New Contact dialog, then exit"))
22- parser.add_option(
23- "-d", "--dbusdaemon", action="store_true", dest="dbusdaemon",
24- help=_("Start Dexter in daemon mode, for use with dbus"))
25 (options, args) = parser.parse_args()
26
27 # Set the logging level to show debug messages.
28@@ -77,17 +75,21 @@
29 files_to_import.append(arg)
30
31 session_bus = dbus.SessionBus()
32- is_running = session_bus.request_name("org.elementary.dexterservice") != \
33- dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER
34+ try:
35+ dexter_object = session_bus.get_object("org.elementary.dexterapp",
36+ "/org/elementary/dexterapp")
37+ is_running = True
38+ except:
39+ is_running = False
40
41 if is_running:
42- dexter_object = session_bus.get_object("org.elementary.dexterservice",
43- "/org/elementary/dexterservice")
44+ dexter_object = session_bus.get_object("org.elementary.dexterserver",
45+ "/org/elementary/dexterserver")
46 logging.info("Application already running")
47 if options.newcontact:
48 method = dexter_object.get_dbus_method("NewContactDialog")
49 method()
50- elif not options.dbusdaemon:
51+ else:
52 method = dexter_object.get_dbus_method("ShowWindow")
53 method()
54 if files_to_import:
55
56=== added file 'bin/dexter-server'
57--- bin/dexter-server 1970-01-01 00:00:00 +0000
58+++ bin/dexter-server 2010-12-20 23:23:53 +0000
59@@ -0,0 +1,350 @@
60+#!/usr/bin/env python
61+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
62+### BEGIN LICENSE
63+# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
64+# This program is free software: you can redistribute it and/or modify it
65+# under the terms of the GNU General Public License version 3, as published
66+# by the Free Software Foundation.
67+#
68+# This program is distributed in the hope that it will be useful, but
69+# WITHOUT ANY WARRANTY; without even the implied warranties of
70+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
71+# PURPOSE. See the GNU General Public License for more details.
72+#
73+# You should have received a copy of the GNU General Public License along
74+# with this program. If not, see <http://www.gnu.org/licenses/>.
75+### END LICENSE
76+
77+import os
78+import sys
79+import logging
80+import gtk
81+import glib
82+import dbus
83+import dbus.service
84+from dbus.mainloop.glib import DBusGMainLoop
85+
86+DBusGMainLoop(set_as_default=True)
87+
88+PROJECT_ROOT_DIRECTORY = os.path.abspath(
89+ os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
90+
91+if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'dexter'))
92+ and PROJECT_ROOT_DIRECTORY not in sys.path):
93+ sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
94+ os.putenv('PYTHONPATH', PROJECT_ROOT_DIRECTORY) # for subprocesses
95+
96+import dexter.backend.models as models
97+import dexter.backend.serverutils as utilities
98+
99+# this is for testing the switch to a fully modular system
100+class DexterServer(dbus.service.Object):
101+ def __init__(self):
102+
103+ if os.path.exists(sys.path[0]+"/data/ui/dexter-main.ui"):
104+ logging.info("Using data from sys.path[0] dir")
105+ self.datadir = sys.path[0]+"/data"
106+ elif os.path.exists("../data/ui/dexter-main.ui"):
107+ logging.info("Using data from current dir")
108+ self.datadir = "../data"
109+ elif os.path.exists("./data/ui/dexter-main.ui"):
110+ logging.info("Running locally")
111+ self.datadir = "./data"
112+ else:
113+ self.datadir = "/usr/share/dexter/"
114+
115+ session_bus = dbus.SessionBus()
116+ is_running = session_bus.request_name("org.elementary.dexterserver") != \
117+ dbus.bus.REQUEST_NAME_REPLY_PRIMARY_OWNER
118+
119+ if is_running:
120+ sys.exit(0)
121+ else:
122+ bus_name = dbus.service.BusName('org.elementary.dexterserver',
123+ bus=dbus.SessionBus())
124+ dbus.service.Object.__init__(self, bus_name,
125+ '/org/elementary/dexterserver')
126+ self.store = models.setup_db(models.DATA_DIR)
127+ models.create_tables(self.store)
128+ models.update_tables(self.store)
129+
130+ #service methods
131+ @dbus.service.method('org.elementary.dexterserver', in_signature='ssssssa(ss)a(ss)aa{ss}as', out_signature='i')
132+ def CreateContact(self, first_name, middle_name, last_name,
133+ organization, birthday, photo_path, emails,
134+ phones, addresses, notes):
135+ if first_name == "None":
136+ first_name = None
137+ if middle_name == "None":
138+ middle_name = None
139+ if last_name == "None":
140+ last_name = None
141+ if organization == "None":
142+ organization = None
143+ if birthday == "None":
144+ birthday = None
145+ if photo_path == "None":
146+ photo_path = None
147+ for address in addresses:
148+ if address["street"] == "None":
149+ address["street"] = None
150+ if address["city"] == "None":
151+ address["city"] = None
152+ if address["state"] == "None":
153+ address["state"] = None
154+ if address["zip"] == "None":
155+ address["zip"] = None
156+ if address["type"] == "None":
157+ address["type"] = None
158+ for note in notes:
159+ if note.strip() == "None" or not note.strip():
160+ note = None
161+ new_id = utilities.create_new_contact(first_name, middle_name, last_name,
162+ organization, birthday, photo_path, emails,
163+ phones, addresses, notes, self.store)
164+ self.ContactAdded(self.convert_to_small_dict(self.store.find(models.Contact, models.Contact.id == new_id).one()))
165+ return new_id
166+
167+ @dbus.service.method('org.elementary.dexterserver')
168+ def DeleteContact(self, id_number):
169+ utilities.delete_by_id(self.store, id_number)
170+ self.ContactsDeleted([id_number])
171+
172+ @dbus.service.method('org.elementary.dexterserver')
173+ def DeleteContacts(self, id_numbers):
174+ for id_number in id_numbers:
175+ utilities.delete_by_id(self.store, id_number)
176+ self.ContactsDeleted(id_numbers)
177+
178+ @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}')
179+ def GetFullList(self):
180+ full_list = []
181+ contacts = self.store.find(models.Contact)
182+ for contact in contacts:
183+ new = self.convert_to_small_dict(contact)
184+ full_list.append(new)
185+ return full_list
186+
187+ @dbus.service.method('org.elementary.dexterserver', out_signature='a{ss}')
188+ def GetContactByID(self, id_number):
189+ contact = self.store.find(models.Contact, models.Contact.id == id_number).one()
190+ if contact:
191+ contact_dict = self.convert_contact_to_dict(contact)
192+ else:
193+ contact_dict = dict()
194+ return contact_dict
195+
196+ @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)')
197+ def GetPhones(self, id_number):
198+ phone_list = []
199+ phones = self.store.find(models.Phone, models.Phone.contact_id == id_number)
200+ for phone in phones:
201+ phone_list.append((self.none_to_string(phone.number), self.none_to_string(phone.phone_type)))
202+ return phone_list
203+
204+ @dbus.service.method('org.elementary.dexterserver', out_signature='a(ss)')
205+ def GetEmails(self, id_number):
206+ email_list = []
207+ emails = self.store.find(models.Email, models.Email.contact_id == id_number)
208+ for email in emails:
209+ email_list.append((self.none_to_string(email.address), self.none_to_string(email.email_type)))
210+ return email_list
211+
212+ @dbus.service.method('org.elementary.dexterserver', out_signature='aa{ss}')
213+ def GetAddresses(self, id_number):
214+ address_list = []
215+ addresses = self.store.find(models.Address, models.Address.contact_id == id_number)
216+ for address in addresses:
217+ adict = dict()
218+ adict["type"] = self.none_to_string(address.address_type)
219+ adict["street"] = self.none_to_string(address.street)
220+ adict["city"] = self.none_to_string(address.city)
221+ adict["state"] = self.none_to_string(address.state)
222+ adict["zip"] = self.none_to_string(address.zip)
223+ address_list.append(adict)
224+ return address_list
225+
226+ @dbus.service.method('org.elementary.dexterserver', out_signature='as')
227+ def GetNotes(self, id_number):
228+ text = ""
229+ for note in self.store.find(models.Note, models.Note.contact_id == id_number):
230+ text += note.text
231+ return [text]
232+
233+ @dbus.service.method('org.elementary.dexterserver')
234+ def GetName(self, emailstring):
235+ """take an email address and return the full name associated with it"""
236+ try:
237+ email = self.store.find(models.Email,
238+ models.Email.address == emailstring).one()
239+ name = email.contact.full_name
240+ except:
241+ name = ""
242+ return name
243+
244+ @dbus.service.method('org.elementary.dexterserver')
245+ def GetEmailAddresses(self, full_name):
246+ """take a full name and return a list of emails associated with it"""
247+ emails = []
248+ name_pieces = full_name.split()
249+ contact = self.store.find(models.Contact,
250+ models.Contact.first_name == name_pieces[0],
251+ models.Contact.last_name == name_pieces[-1]).any()
252+ for email in contact.emails:
253+ emails.append(email.address)
254+ return emails
255+
256+ @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='aa{ss}')
257+ def SearchContacts(self, search_string):
258+ """search for contacts that contain a string
259+
260+ takes a search string, and returns a list of name/email strings.
261+ """
262+ results = []
263+ resultlist = []
264+ results = utilities.search_store(search_string, self.store)
265+ for contact in results:
266+ new = dict()
267+ new["full_name"] = contact.full_name
268+ new["id"] = str(contact.id)
269+ if contact.last_name:
270+ new["last_name"] = contact.last_name
271+ else:
272+ new["last_name"] = contact.first_name
273+ resultlist.append(new)
274+ return resultlist
275+
276+ @dbus.service.method('org.elementary.dexterserver', in_signature='s', out_signature='as')
277+ def AutocompleteContact(self, search_string):
278+ """search for contacts that contain a string
279+
280+ takes a search string, and returns a list of name/email strings.
281+ """
282+ results = []
283+ resultlist = []
284+ results = utilities.search_store(search_string, self.store)
285+ for contact in results:
286+ for email in contact.emails:
287+ resultlist.append(contact.full_name + " <" + email.address + ">")
288+ return resultlist
289+
290+ @dbus.service.method('org.elementary.dexterserver')
291+ def ShowWindow(self):
292+ """present the running dexter window, or start one"""
293+ session_bus = dbus.SessionBus()
294+ try:
295+ dexter_object = session_bus.get_object("org.elementary.dexterapp",
296+ "/org/elementary/dexterapp")
297+ is_running = True
298+ except:
299+ is_running = False
300+ if is_running:
301+ self.WindowRequested()
302+ else:
303+ try:
304+ glib.spawn_async([self.datadir + "/../bin/dexter"])
305+ except glib.GError:
306+ glib.spawn_async(["/usr/bin/dexter"])
307+
308+ @dbus.service.method('org.elementary.dexterserver')
309+ def NewContactDialog(self):
310+ """present a new contact window"""
311+ session_bus = dbus.SessionBus()
312+ try:
313+ dexter_object = session_bus.get_object("org.elementary.dexterapp",
314+ "/org/elementary/dexterapp")
315+ is_running = True
316+ except:
317+ is_running = False
318+ if is_running:
319+ self.ContactDialogRequested()
320+ else:
321+ glib.spawn_async(["/usr/bin/dexter", "-n"])
322+
323+ @dbus.service.method('org.elementary.dexterserver')
324+ def CancelImport(self):
325+ self.CancelImportRequested()
326+
327+ @dbus.service.method('org.elementary.dexterserver', in_signature='as')
328+ def ImportVcardPath(self, paths):
329+ self.VcardImportRequested(paths)
330+
331+ @dbus.service.method('org.elementary.dexterserver')
332+ def DBCommit(self):
333+ self.store.commit()
334+
335+ @dbus.service.method('org.elementary.dexterserver')
336+ def QuitServer(self):
337+ self.store.commit()
338+ self.ServerClosed()
339+ gtk.main_quit()
340+
341+ #signals
342+ @dbus.service.signal('org.elementary.dexterserver')
343+ def ContactAdded(self, contact):
344+ pass
345+
346+ @dbus.service.signal('org.elementary.dexterserver')
347+ def ContactsDeleted(self, contact_ids):
348+ if not self.store.find(models.Contact).count():
349+ self.ContactsCleared()
350+
351+ @dbus.service.signal('org.elementary.dexterserver')
352+ def ContactsCleared(self):
353+ pass
354+
355+ @dbus.service.signal('org.elementary.dexterserver')
356+ def WindowRequested(self):
357+ pass
358+
359+ @dbus.service.signal('org.elementary.dexterserver')
360+ def ContactDialogRequested(self):
361+ pass
362+
363+ @dbus.service.signal('org.elementary.dexterserver')
364+ def CancelImportRequested(self):
365+ pass
366+
367+ @dbus.service.signal('org.elementary.dexterserver')
368+ def VcardImportRequested(self, paths):
369+ pass
370+
371+ @dbus.service.signal('org.elementary.dexterserver')
372+ def ServerClosed(self):
373+ pass
374+
375+ #helper methods
376+ def convert_contact_to_dict(self, contact):
377+ new = dict()
378+ new["id"] = str(contact.id)
379+ new["photo"] = self.none_to_string(contact.photo)
380+ new["first_name"] = contact.first_name
381+ new["middle_name"] = self.none_to_string(contact.middle_name)
382+ new["last_name"] = self.none_to_string(contact.last_name)
383+ new["full_name"] = contact.full_name
384+ new["organization"] = self.none_to_string(contact.organization)
385+ new["birthday"] = str(self.none_to_string(contact.birthday))
386+ return new
387+
388+ def convert_to_small_dict(self, contact):
389+ new = dict()
390+ new["full_name"] = contact.full_name
391+ new["id"] = str(contact.id)
392+ if contact.last_name:
393+ new["last_name"] = contact.last_name
394+ else:
395+ new["last_name"] = contact.first_name
396+ return new
397+
398+ def none_to_string(self, data):
399+ if not data:
400+ data = "None"
401+ return data
402+
403+if __name__ == "__main__":
404+ service = DexterServer()
405+ try:
406+ gtk.main()
407+ except:
408+ service.ServerClosed()
409+ service.store.commit()
410
411=== modified file 'data/ui/dexter-main.ui'
412--- data/ui/dexter-main.ui 2010-12-13 22:23:48 +0000
413+++ data/ui/dexter-main.ui 2010-12-20 23:23:53 +0000
414@@ -410,31 +410,48 @@
415 </packing>
416 </child>
417 <child>
418- <object class="GtkHBox" id="hbox_progress">
419+ <object class="GtkVBox" id="vbox_progress">
420 <child>
421- <object class="GtkVBox" id="vbox1">
422+ <object class="GtkLabel" id="label_progress">
423 <property name="visible">True</property>
424+ </object>
425+ <packing>
426+ <property name="position">0</property>
427+ </packing>
428+ </child>
429+ <child>
430+ <object class="GtkHBox" id="hbox_progress">
431 <child>
432- <object class="GtkProgressBar" id="progressbar"/>
433+ <object class="GtkVBox" id="vbox8">
434+ <property name="visible">True</property>
435+ <child>
436+ <object class="GtkProgressBar" id="progressbar"/>
437+ <packing>
438+ <property name="padding">8</property>
439+ <property name="position">0</property>
440+ </packing>
441+ </child>
442+ </object>
443 <packing>
444- <property name="padding">8</property>
445+ <property name="padding">4</property>
446 <property name="position">0</property>
447 </packing>
448 </child>
449- </object>
450- <packing>
451- <property name="padding">4</property>
452- <property name="position">0</property>
453- </packing>
454- </child>
455- <child>
456- <object class="GtkButton" id="button_cancel">
457- <property name="visible">True</property>
458- <property name="can_focus">True</property>
459- <property name="receives_default">True</property>
460- <property name="image">image1</property>
461- <property name="relief">none</property>
462- <signal name="clicked" handler="_on_button_cancel_clicked"/>
463+ <child>
464+ <object class="GtkButton" id="button_cancel">
465+ <property name="visible">True</property>
466+ <property name="can_focus">True</property>
467+ <property name="receives_default">True</property>
468+ <property name="image">image1</property>
469+ <property name="relief">none</property>
470+ <signal name="clicked" handler="_on_button_cancel_clicked"/>
471+ </object>
472+ <packing>
473+ <property name="expand">False</property>
474+ <property name="fill">False</property>
475+ <property name="position">1</property>
476+ </packing>
477+ </child>
478 </object>
479 <packing>
480 <property name="expand">False</property>
481
482=== modified file 'debian/changelog'
483--- debian/changelog 2010-12-12 20:24:32 +0000
484+++ debian/changelog 2010-12-20 23:23:53 +0000
485@@ -3,7 +3,7 @@
486 * fixed bug 688411, dexter should START after installation from PPA
487 now, lol
488
489- -- Allen Lowe <allen@allen-delltop> Sun, 12 Dec 2010 13:17:12 -0700
490+ -- Allen Lowe <lallenlowe@allen-work-desktop> Mon, 20 Dec 2010 16:14:52 -0700
491
492 dexter (0.15) maverick; urgency=low
493
494
495=== modified file 'dexter/SimpleGtkbuilderApp.py'
496--- dexter/SimpleGtkbuilderApp.py 2010-12-10 17:59:44 +0000
497+++ dexter/SimpleGtkbuilderApp.py 2010-12-20 23:23:53 +0000
498@@ -1,3 +1,19 @@
499+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
500+### BEGIN LICENSE
501+# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
502+# This program is free software: you can redistribute it and/or modify it
503+# under the terms of the GNU General Public License version 3, as published
504+# by the Free Software Foundation.
505+#
506+# This program is distributed in the hope that it will be useful, but
507+# WITHOUT ANY WARRANTY; without even the implied warranties of
508+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
509+# PURPOSE. See the GNU General Public License for more details.
510+#
511+# You should have received a copy of the GNU General Public License along
512+# with this program. If not, see <http://www.gnu.org/licenses/>.
513+### END LICENSE
514+
515 """
516 SimpleGladeApp.py
517 Module that provides an object oriented abstraction to pygtk and gtkbuilder
518@@ -5,20 +21,6 @@
519 based on ideas from SimpleGladeBuilder by Sandino Flores Moreno
520 """
521
522-# This library is free software; you can redistribute it and/or
523-# modify it under the terms of the GNU Lesser General Public
524-# License as published by the Free Software Foundation; version 3.
525-#
526-# This library is distributed in the hope that it will be useful,
527-# but WITHOUT ANY WARRANTY; without even the implied warranty of
528-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
529-# Lesser General Public License for more details.
530-#
531-# You should have received a copy of the GNU Lesser General Public
532-# License along with this library; if not, write to the Free Software
533-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
534-# USA
535-
536 import gtk
537 import logging
538
539
540=== modified file 'dexter/app.py'
541--- dexter/app.py 2010-12-12 22:50:01 +0000
542+++ dexter/app.py 2010-12-20 23:23:53 +0000
543@@ -1,26 +1,28 @@
544-# Authors:
545-# Andrew Higginson
546-#
547-# This program is free software; you can redistribute it and/or modify it under
548-# the terms of the GNU General Public License as published by the Free Software
549-# Foundation; version 3.
550-#
551-# This program is distributed in the hope that it will be useful, but WITHOUT
552-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
553-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
554-# details.
555-#
556-# You should have received a copy of the GNU General Public License along with
557-# this program; if not, write to the Free Software Foundation, Inc.,
558-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
559+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
560+### BEGIN LICENSE
561+# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
562+# This program is free software: you can redistribute it and/or modify it
563+# under the terms of the GNU General Public License version 3, as published
564+# by the Free Software Foundation.
565+#
566+# This program is distributed in the hope that it will be useful, but
567+# WITHOUT ANY WARRANTY; without even the implied warranties of
568+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
569+# PURPOSE. See the GNU General Public License for more details.
570+#
571+# You should have received a copy of the GNU General Public License along
572+# with this program. If not, see <http://www.gnu.org/licenses/>.
573+### END LICENSE
574
575 import sys
576 import os
577 import warnings
578 import gtk
579 import glib
580+import dbus
581 import gobject
582 import locale
583+import datetime
584 import Queue
585
586 from dexter.frontend.dialogs.about import AboutDialog
587@@ -50,30 +52,28 @@
588 self.icons = gtk.icon_theme_get_default()
589 self.welcoming = False
590 self.q = None
591+ self.selected_id = None
592 self.config = DexterConfig()
593
594 # Setup Translations
595 self._setup_translations()
596
597 # Setup Data
598+ self._setup_dbus_client()
599 self._setup_data()
600
601 # Setup Import from CLI
602 self._setup_import()
603
604- myservice = dexdbus.DexterService(self.icons, self.datadir, self)
605
606 #run just the newcontact dialog
607 if options.newcontact:
608 self._purpose = RUN_CONTACT_DIALOG
609 self.contact_dialog = NewContactDialog(self.icons, self.datadir)
610 self.contact_dialog.standalone = True
611- # run just the dbus daemon
612- elif options.dbusdaemon:
613- self._purpose = RUN_DAEMON
614- myservice.daemon = True
615 # Run the application.
616 else:
617+ myservice = dexdbus.DexterApp()
618 self._purpose = RUN_APP
619
620 if options.newcontact:
621@@ -88,14 +88,47 @@
622 # Load any config items (i.e. saved hpane width)
623 self._load_config()
624
625- if not self.contacts.count():
626+ if not len(self.contacts):
627 self._setup_welcome()
628 else:
629 self._setup_main_screen()
630- self.refresh_list(self.contacts)
631+ self.refresh_list()
632 if self.files_to_import:
633 self._import_vcard_path(self.files_to_import)
634
635+ def _setup_dbus_client(self):
636+ self.session_bus = dbus.SessionBus()
637+ #############TODO: USE SERVICE FILE INSTEAD############################
638+ try:
639+ self.dexter_object = self.session_bus.get_object("org.elementary.dexterserver",
640+ "/org/elementary/dexterserver")
641+ except:
642+ try:
643+ glib.spawn_async([self.datadir + "/../bin/dexter-server"])
644+ except glib.GError:
645+ glib.spawn_async(["/usr/bin/dexter-server"])
646+ import time; time.sleep(0.5)
647+ self.dexter_object = self.session_bus.get_object("org.elementary.dexterserver",
648+ "/org/elementary/dexterserver")
649+ #############TODO: USE SERVICE FILE INSTEAD############################
650+ self.GetFullList = self.dexter_object.get_dbus_method("GetFullList")
651+ self.GetContactByID = self.dexter_object.get_dbus_method("GetContactByID")
652+ self.GetAddresses = self.dexter_object.get_dbus_method("GetAddresses")
653+ self.GetPhones = self.dexter_object.get_dbus_method("GetPhones")
654+ self.GetEmails = self.dexter_object.get_dbus_method("GetEmails")
655+ self.GetNotes = self.dexter_object.get_dbus_method("GetNotes")
656+ self.CreateContact = self.dexter_object.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver')
657+ self.DeleteContact = self.dexter_object.get_dbus_method("DeleteContact")
658+ self.DeleteContacts = self.dexter_object.get_dbus_method("DeleteContacts")
659+ self.DBCommit = self.dexter_object.get_dbus_method("DBCommit")
660+ self.dexter_object.connect_to_signal("ContactAdded", self._on_contact_created_dbus_signal, 'org.elementary.dexterserver')
661+ self.dexter_object.connect_to_signal("ContactsDeleted", self._on_contacts_deleted_dbus_signal, 'org.elementary.dexterserver')
662+ self.dexter_object.connect_to_signal("ContactsCleared", self._on_contacts_cleared_dbus_signal, 'org.elementary.dexterserver')
663+ self.dexter_object.connect_to_signal("WindowRequested", self._on_window_requested_dbus_signal, 'org.elementary.dexterserver')
664+ self.dexter_object.connect_to_signal("ContactDialogRequested", self._on_contact_dialog_requested_dbus_signal, 'org.elementary.dexterserver')
665+ self.dexter_object.connect_to_signal("CancelImportRequested", self._on_cancel_import_requested_dbus_signal, 'org.elementary.dexterserver')
666+ self.dexter_object.connect_to_signal("VcardImportRequested", self._on_vcard_import_requested_dbus_signal, 'org.elementary.dexterserver')
667+ self.dexter_object.connect_to_signal("ServerClosed", self._on_server_closed_dbus_signal, 'org.elementary.dexterserver')
668
669 # Private Functions
670 def _setup_translations(self):
671@@ -115,11 +148,8 @@
672 self.files_to_import.append(arg)
673
674 def _setup_data(self):
675- """setup storm Store, and get a list of contacts from the database"""
676- self.store = models.setup_db(models.DATA_DIR)
677- models.create_tables(self.store)
678- models.update_tables(self.store)
679- self.contacts = self.store.find(models.Contact)
680+ """get a list of contacts from the database"""
681+ self.contacts = self.GetFullList()
682 self.importing = False
683
684 def _setup_contact_list(self):
685@@ -147,7 +177,7 @@
686 """setup the necessary fields for live searching"""
687 self.toolbutton1 = self.builder.get_object("toolbutton1")
688 self.search_bar = SearchBar(_("Type to search..."),
689- self.store)
690+ self.dexter_object)
691 self.toolbutton1.add(self.search_bar)
692 self.search_bar.show()
693 self.search_bar.connect("changed", self._on_search_bar_changed)
694@@ -181,14 +211,43 @@
695 def _save_config(self):
696 hpane_width = self.hpaned_main.get_position()
697 self.config.set("general", "hpane_width", hpane_width)
698-
699+
700
701 # Callbacks
702+ def _on_contact_created_dbus_signal(self, contact):
703+ if self.welcoming:
704+ self._setup_main_screen()
705+ if not self.importing:
706+ self.refresh_list()
707+ self.select_by_id(int(contact["id"]))
708+ self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts")))
709+
710+ def _on_contacts_deleted_dbus_signal(self, contact_ids):
711+ self.refresh_list()
712+
713+ def _on_contacts_cleared_dbus_signal(self):
714+ if not self.vbox_welcome in self.vbox_main.get_children():
715+ self._setup_welcome()
716+
717+ def _on_window_requested_dbus_signal(self):
718+ self.window_main.present()
719+
720+ def _on_contact_dialog_requested_dbus_signal(self):
721+ self._on_toolbutton_new_contact_clicked(None)
722+
723+ def _on_cancel_import_requested_dbus_signal(self):
724+ self.q.request_stop = True
725+
726+ def _on_vcard_import_requested_dbus_signal(self, paths):
727+ self._import_vcard_path(paths)
728+
729+ def _on_server_closed_dbus_signal(self):
730+ self._setup_dbus_client()
731+
732 def _on_window_main_delete_event(self, widget, event):
733 # Clean up code for saving application state should be added here.
734- self.store.commit()
735+ self.DBCommit()
736 self._save_config()
737-
738 gtk.main_quit()
739
740 def _on_menuitem_quit_activated(self, widget):
741@@ -293,8 +352,7 @@
742 gtk.gdk.CURRENT_TIME)
743
744 def _on_menuitem_merge_activated(self, widget):
745- self.store.flush()
746- result = utilities.merge_contacts(self.get_selected_contacts(), self.store)
747+ result = utilities.merge_contacts(self.get_selected_contacts(), self.dexter_object)
748 if type(result) == dict:
749 merge = MergeDialog()
750 merge.set_transient_for(self.window_main)
751@@ -303,11 +361,11 @@
752 if response == gtk.RESPONSE_OK:
753 result = merge.data
754 merge.destroy()
755- id_number = utilities.create_new_contact(result["first"][0], result["middle"][0],
756- result["last"][0], result["org"][0],
757- result["birthday"][0], result["photo"],
758- result["emails"], result["phones"], result["addresses"],
759- result["notes"], self.store)
760+ id_number = self.CreateContact(result["first"][0], result["middle"][0],
761+ result["last"][0], result["org"][0],
762+ result["birthday"][0], result["photo"],
763+ result["emails"], result["phones"],
764+ result["addresses"], result["notes"],)
765 elif type(result) == int:
766 id_number = result
767 else:
768@@ -328,16 +386,16 @@
769 chooser.set_default_response(gtk.RESPONSE_OK)
770 chooser.set_current_folder(os.path.expanduser("~")) # set to users home folder
771 if len(contacts) == 1:
772- if contacts[0].last_name:
773- chooser.set_current_name(contacts[0].first_name.lower() + "-" +
774- contacts[0].last_name.lower() + ".vcf")
775+ if contacts[0]["last_name"]:
776+ chooser.set_current_name(contacts[0]["first_name"].lower() + "-" +
777+ contacts[0]["last_name"].lower() + ".vcf")
778 else:
779- chooser.set_current_name(contacts[0].first_name.lower() + ".vcf")
780+ chooser.set_current_name(contacts[0]["first_name"].lower() + ".vcf")
781 else:
782 chooser.set_current_name("contacts.vcf")
783 response = chooser.run()
784 if response == gtk.RESPONSE_OK:
785- utilities.export_to_vcard(contacts, chooser.get_filename())
786+ utilities.export_to_vcard(contacts, chooser.get_filename(), self.dexter_object)
787 chooser.destroy()
788
789 def _on_welcome_import_clicked(self, widget):
790@@ -345,7 +403,6 @@
791
792 def _on_menuitem_import_activated(self, widget):
793 """user selects the import from vcard action"""
794- self.store.commit()
795 chooser = gtk.FileChooserDialog(title=None,
796 action=gtk.FILE_CHOOSER_ACTION_OPEN,
797 buttons=(gtk.STOCK_CANCEL,
798@@ -363,7 +420,6 @@
799 response = chooser.run()
800 if response == gtk.RESPONSE_OK:
801 self.search_bar.hint()
802- self.refresh_list(self.contacts)
803 vcard_paths = chooser.get_filenames()
804 self._import_vcard_path(vcard_paths)
805 chooser.destroy()
806@@ -375,12 +431,15 @@
807 import_thread.start() # start parsing vcard in a separate thread
808 self.timer = gobject.timeout_add(100, self.import_timeout)#create a timer for adding contacts, so as to not block the GUI
809 timer2 = gobject.timeout_add(200, self.update_timeout)# a timer to make sure the UI gets refreshed sometimes during import
810+ self.vbox_progress.show()
811+ self.label_progress.set_text(_("Importing Contacts..."))
812 self.hbox_progress.show()
813 self.progressbar.show()
814+ self.menuitem_import.set_sensitive(False)
815
816 def _on_button_photo_clicked(self, widget):
817 """photo button clicked, open photo in external viewer"""
818- photo_name = self.get_selected_contact().photo
819+ photo_name = self.get_selected_contact()["photo"]
820 try:
821 glib.spawn_async(["/usr/bin/xdg-open", models.IMAGE_DIR + \
822 str(photo_name)])
823@@ -395,9 +454,8 @@
824
825 def _on_store_changed(self):
826 """run when a contact has been created, edited, merged, or deleted"""
827- self.store.commit()
828+ self.DBCommit()
829 self.search_bar.hint()
830- self.refresh_list()
831 self.clear_contact_viewer()
832 self.search_bar.result_sets = {}
833 self.search_bar.result_set = []
834@@ -418,8 +476,10 @@
835 response = md.run()
836 md.destroy()
837 if response == gtk.RESPONSE_YES:
838- for contact in selected_contacts :
839- utilities.delete_contact(self.store, contact)
840+ ids = []
841+ for contact in selected_contacts:
842+ ids.append(int(contact["id"]))
843+ self.DeleteContacts(ids)
844 self._on_store_changed()
845
846 def _on_welcome_create_clicked(self, widget):
847@@ -429,14 +489,11 @@
848 """open a dialog to create a new contact"""
849 new = NewContactDialog(self.icons, self.datadir)
850 new.dialog.set_transient_for(self.window_main)
851- self.store.flush()
852- new.set_store(self.store)
853 response = new.run()
854 id_number = new.new_contact_id
855 new.dialog.destroy()
856 if response == gtk.RESPONSE_OK:
857 self._on_store_changed()
858- self.select_by_id(id_number)
859 if self.welcoming:
860 self._setup_main_screen()
861
862@@ -445,8 +502,7 @@
863 contact = self.get_selected_contact()
864 edit = NewContactDialog(self.icons, self.datadir)
865 edit.dialog.set_transient_for(self.window_main)
866- self.store.flush()
867- edit.set_contact_edit(contact, self.store)
868+ edit.set_contact_edit(contact)
869 edit.populate()
870 response = edit.run()
871 id_number = edit.new_contact_id
872@@ -521,23 +577,13 @@
873 if not self.q.request_stop:
874 try:
875 item = self.q.get(True, 2)
876- new_id = utilities.create_new_contact(item[0], item[1], item[2],
877- item[3], item[4], item[5],
878- item[6], item[7], item[8],
879- item[9], self.store)
880+ new_id = self.CreateContact(item[0], item[1], item[2], item[3],
881+ item[4], item[5], item[6], item[7],
882+ item[8], item[9])
883 self.last_contact_imported = new_id
884- contact = self.store.find(models.Contact, models.Contact.id == new_id).one()
885+ contact = self.GetContactByID(new_id)
886 self.progressbar.pulse()
887- if contact.last_name:
888- # add the newly imported contact to the contact list treeview
889- self.contact_model.append([contact.full_name, contact.id,
890- contact.last_name])
891- else:
892- self.contact_model.append([contact.full_name, contact.id,
893- contact.first_name])
894 self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts")))
895- if self.welcoming:
896- self._setup_main_screen()
897 self.importing = True
898 return True
899 except Queue.Empty:
900@@ -556,14 +602,17 @@
901 def finish_importing(self):
902 self.progressbar.hide()
903 self.hbox_progress.hide()
904+ self.vbox_progress.hide()
905+ self.label_progress.set_text("")
906 self.progressbar.set_fraction(0.0)
907- self.timer = gobject.timeout_add(100, self.store.commit)
908+ self.DBCommit()
909 self.importing = False
910 self.refresh_list()
911 if self.last_contact_imported:
912 self.select_by_id(self.last_contact_imported)
913 self.search_bar.set_sensitive(True)
914 self.timer = None
915+ self.menuitem_import.set_sensitive(True)
916
917 def refresh_list(self, contact_list="empty"):
918 """
919@@ -572,27 +621,37 @@
920 get a list from the database
921 """
922 if contact_list == "empty":
923- self.contacts = self.store.find(models.Contact)
924+ self.contacts = self.GetFullList()
925 contact_list = self.contacts
926- self.search_bar.refresh(self.contacts.count()) # make sure the searchbar has the updated contact list
927+ elif not contact_list:
928+ pass
929+ self.search_bar.refresh(len(self.contacts)) # give searchbar the updated contact numbers
930 self.contact_model.clear()
931 for contact in contact_list:
932- if contact.last_name:
933- self.contact_model.append([contact.full_name, contact.id,
934- contact.last_name])
935- else:
936- self.contact_model.append([contact.full_name, contact.id,
937- contact.first_name])
938+ self.insert_in_list(contact)
939 self.num_contacts.set_text(" %i %s" % (len(self.contact_model), _("Contacts")))
940 #clearing the list unselects any contacts, so make these items insensitive
941 self.toolbutton_edit_contact.set_sensitive(False)
942 self.menuitem_merge.set_sensitive(False)
943 self.toolbutton_delete_contact.set_sensitive(False)
944 self.menuitem_export.set_sensitive(False)
945- if not self.contacts.count():
946+ if not len(self.contacts):
947 if not self.vbox_welcome in self.vbox_main.get_children():
948 self._setup_welcome()
949
950+ def insert_in_list(self, contact):
951+ self.contact_model.append([contact["full_name"], int(contact["id"]),
952+ contact["last_name"]])
953+
954+ def remove_from_list(self, contact_ids=[]):
955+ row_iters = []
956+ for row in self.contact_model:
957+ if row[1] in contact_ids:
958+ rowiter = self.contact_model.get_iter(row.path)
959+ row_iters.append(rowiter)
960+ for rowiter in row_iters:
961+ self.contact_model.remove(rowiter)
962+
963 #this is still here for compatibility with code I haven't updated yet
964 def get_selected_contact(self):
965 """get the contact that is currently selected from the database"""
966@@ -606,8 +665,7 @@
967 treeiter = self.contact_model.get_iter(path)
968 try:
969 id = selection[0].get_value(treeiter, 1)
970- contact = self.store.find(models.Contact,
971- models.Contact.id == id).one()
972+ contact = self.GetContactByID(id)
973 except TypeError:
974 contact = None
975 return contact
976@@ -620,8 +678,7 @@
977 treeiter = self.contact_model.get_iter(path)
978 try:
979 id = selection[0].get_value(treeiter, 1)
980- contact = self.store.find(models.Contact,
981- models.Contact.id == id).one()
982+ contact = self.GetContactByID(id)
983 except TypeError:
984 contact = None
985 contactlist.append(contact)
986@@ -658,72 +715,79 @@
987 def populate_viewer(self, contact):
988 """fill the contents of the contact viewer with the given contact"""
989 note_space = 0
990- self.label_name.set_text(contact.full_name)
991- if contact.organization:
992- self.label_organization.set_text(contact.organization)
993+ self.label_name.set_text(contact["full_name"])
994+ if contact["organization"] != "None":
995+ self.label_organization.set_text(contact["organization"])
996 else:
997 self.label_organization.set_text("")
998- if contact.photo:
999+ if contact["photo"] != "None":
1000 self.button_photo.show()
1001 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(models.IMAGE_DIR +
1002- str(contact.photo),
1003+ str(contact["photo"]),
1004 60, 60)
1005 self.image_photo.set_from_pixbuf(pixbuf)
1006 else:
1007 self.button_photo.hide()
1008- if contact.birthday:
1009+ if contact["birthday"] != "None":
1010+ birthdaylist = contact["birthday"].split("-")
1011+ birthday = datetime.datetime(int(birthdaylist[0]),
1012+ int(birthdaylist[1]),
1013+ int(birthdaylist[2]))
1014 self.vbox_column_header.pack_start(HeaderLabel(_("Birthdate")),
1015 expand=False, fill=True, padding=0)
1016 self.vbox_column_data.pack_start(DataLabel(
1017- contact.birthday.strftime("%B %d, %Y")),
1018+ birthday.strftime("%B %d, %Y")),
1019 expand=False, fill=True, padding=0)
1020 self.blank_line()
1021- if contact.emails.count():
1022- for email in contact.emails:
1023- email_label = DataLabel(email.address)
1024+ emails = self.GetEmails(int(contact["id"]))
1025+ if len(emails):
1026+ for email in emails:
1027+ email_label = DataLabel(email[0])
1028 email_label_markup = "<a href=\"%s\" title=\"%s\">%s</a>" % \
1029- ("mailto:"+email.address.strip(),
1030- _("Send email..."),email.address.strip())
1031+ ("mailto:"+email[0].strip(),
1032+ _("Send email..."),email[0].strip())
1033 email_label.set_markup(email_label_markup)
1034- self.vbox_column_header.pack_start(HeaderLabel(email.email_type),
1035+ self.vbox_column_header.pack_start(HeaderLabel(email[1]),
1036 expand=False, fill=True, padding=0)
1037 self.vbox_column_data.pack_start(email_label,
1038 expand=False, fill=True, padding=0)
1039 self.blank_line()
1040- if contact.phones.count():
1041- for phone in contact.phones:
1042- self.vbox_column_header.pack_start(HeaderLabel(phone.phone_type),
1043+ phones = self.GetPhones(int(contact["id"]))
1044+ if len(phones):
1045+ for phone in phones:
1046+ self.vbox_column_header.pack_start(HeaderLabel(phone[1]),
1047 expand=False, fill=True, padding=0)
1048- self.vbox_column_data.pack_start(DataLabel(phone.number),
1049+ self.vbox_column_data.pack_start(DataLabel(phone[0]),
1050 expand=False, fill=True, padding=0)
1051 self.blank_line()
1052- if contact.addresses.count():
1053- for address in contact.addresses:
1054+ addresses = self.GetAddresses(int(contact["id"]))
1055+ if len(addresses):
1056+ for address in addresses:
1057 full_address = ""
1058 multi_line_address = ""
1059 spaced = False
1060 adr_label = DataLabel("")
1061- if address.address_type:
1062+ if address["type"] != "None":
1063 self.vbox_column_header.pack_start(
1064- HeaderLabel(address.address_type),
1065+ HeaderLabel(address["type"]),
1066 expand=False, fill=True, padding=0)
1067 else:
1068 self.vbox_column_header.pack_start(
1069 HeaderLabel(_("Address")),
1070 expand=False, fill=True, padding=0)
1071- if address.street:
1072- full_address = address.street
1073- multi_line_address = address.street
1074+ if address["street"] != "None":
1075+ full_address = address["street"]
1076+ multi_line_address = address["street"]
1077 spaced = True
1078 else:
1079 streetlabel = None
1080 text = ""
1081- if address.city:
1082- text += "%s, " % (address.city)
1083- if address.state:
1084- text += "%s " % (address.state)
1085- if address.zip:
1086- text += "%s" % (address.zip)
1087+ if address["city"] != "None":
1088+ text += "%s, " % (address["city"])
1089+ if address["state"] != "None":
1090+ text += "%s " % (address["state"])
1091+ if address["zip"] != "None":
1092+ text += "%s" % (address["zip"])
1093 if text:
1094 if spaced:
1095 self.vbox_column_header.pack_start(gtk.Label(),
1096@@ -740,11 +804,12 @@
1097 self.vbox_column_data.pack_start(adr_label,
1098 expand=False, fill=True, padding=0)
1099 self.blank_line()
1100- for note in contact.notes:
1101- self.vbox_column_header.pack_start(HeaderLabel(_("Note")),
1102- expand=False, fill=True, padding=0)
1103- self.vbox_column_data.pack_start(DataLabel(note.text),
1104- expand=False, fill=True, padding=note_space)
1105+ for note in self.GetNotes(int(contact["id"])):
1106+ if note.strip() != "None" and note.strip():
1107+ self.vbox_column_header.pack_start(HeaderLabel(_("Note")),
1108+ expand=False, fill=True, padding=0)
1109+ self.vbox_column_data.pack_start(DataLabel(note), expand=False,
1110+ fill=True, padding=note_space)
1111 self.vbox_column_header.show_all()
1112 self.vbox_column_data.show_all()
1113
1114
1115=== modified file 'dexter/backend/ddbus.py'
1116--- dexter/backend/ddbus.py 2010-12-13 20:18:13 +0000
1117+++ dexter/backend/ddbus.py 2010-12-20 23:23:53 +0000
1118@@ -15,118 +15,16 @@
1119 ### END LICENSE
1120
1121
1122-import datetime
1123 import dbus
1124 import dbus.service
1125 from dbus.mainloop.glib import DBusGMainLoop
1126
1127 DBusGMainLoop(set_as_default=True)
1128
1129-from dexter.frontend.dialogs.new_contact import NewContactDialog
1130-import dexter.backend.models as models
1131-import dexter.backend.utilities as utilities
1132-from storm.expr import Or
1133
1134-class DexterService(dbus.service.Object):
1135- def __init__(self, icons, datadir, app=None):
1136- bus_name = dbus.service.BusName('org.elementary.dexterservice',
1137+class DexterApp(dbus.service.Object):
1138+ def __init__(self):
1139+ bus_name = dbus.service.BusName('org.elementary.dexterapp',
1140 bus=dbus.SessionBus())
1141 dbus.service.Object.__init__(self, bus_name,
1142- '/org/elementary/dexterservice')
1143- self.daemon = False
1144- self.datadir = datadir
1145- self.icons = icons
1146- self.app = app
1147- if self.app:
1148- self.store = self.app.store
1149- else:
1150- self.store = models.setup_db(models.DATA_DIR)
1151-
1152- @dbus.service.method('org.elementary.dexterservice')
1153- def ShowWindow(self):
1154- """present the running dexter window, or start one"""
1155- if not self.daemon:
1156- self.app.window_main.present()
1157- else:
1158- self.app.window_main.show()
1159- self.app.refresh_list()
1160- self.daemon = False
1161-
1162- @dbus.service.method('org.elementary.dexterservice')
1163- def NewContactDialog(self):
1164- """present a new contact window"""
1165- new = NewContactDialog(self.icons, self.datadir)
1166- new.dialog.set_transient_for(self.app.window_main)
1167- self.store.flush()
1168- new.set_store(self.store)
1169- new.called_from_dbus = True
1170- new.dialog.show_all()
1171- return True
1172-
1173- @dbus.service.method('org.elementary.dexterservice')
1174- def CancelImport(self):
1175- if self.app.q:
1176- self.app.q.request_stop = True
1177-
1178- @dbus.service.method('org.elementary.dexterservice', in_signature='as')
1179- def ImportVcardPath(self, paths):
1180- self.app._import_vcard_path(paths)
1181-
1182- @dbus.service.method('org.elementary.dexterservice')
1183- def CreateNewSimple(self, first_name, middle_name=None, last_name=None,
1184- organization=None, birthdaystring=None, photo_path=None, email=None,
1185- phone=None, note=None):
1186- try:
1187- birthdaylist = birthdaystring.split("-")
1188- birthday = datetime.datetime(int(birthdaylist[0]),
1189- int(birthdaylist[1]),
1190- int(birthdaylist[2]))
1191- except:
1192- birthday = None
1193- new_id = utilities.create_new_contact(first_name, middle_name, last_name, organization,
1194- birthday, photo_path, [(email, None)],
1195- [(phone, None)], [], [note], self.app.store)
1196- self.app.store.commit()
1197- self.ContactAdded(new_id)
1198- return True
1199-
1200- @dbus.service.method('org.elementary.dexterservice')
1201- def GetName(self, emailstring):
1202- """take an email address and return the full name associated with it"""
1203- try:
1204- email = self.store.find(models.Email,
1205- models.Email.address == emailstring).one()
1206- name = email.contact.full_name
1207- except:
1208- name = ""
1209- return name
1210-
1211- @dbus.service.method('org.elementary.dexterservice')
1212- def GetEmailAddresses(self, full_name):
1213- """take a full name and return a list of emails associated with it"""
1214- emails = []
1215- name_pieces = full_name.split()
1216- contact = self.store.find(models.Contact,
1217- models.Contact.first_name == name_pieces[0],
1218- models.Contact.last_name == name_pieces[-1]).any()
1219- for email in contact.emails:
1220- emails.append(email.address)
1221- return emails
1222-
1223- @dbus.service.method('org.elementary.dexterservice', in_signature='s', out_signature='as')
1224- def SearchContacts(self, search_string):
1225- """search for contacts that contain a string
1226-
1227- takes a search string, and returns a list of name/email strings.
1228- """
1229- results = []
1230- resultlist = []
1231- results = utilities.search_store(search_string, self.store)
1232- for contact in results:
1233- for email in contact.emails:
1234- resultlist.append(contact.full_name + " <" + email.address + ">")
1235- return resultlist
1236-
1237- @dbus.service.signal('org.elementary.dexterservice')
1238- def ContactAdded(self, contact_id):
1239- pass
1240+ '/org/elementary/dexterapp')
1241
1242=== modified file 'dexter/backend/models.py'
1243--- dexter/backend/models.py 2010-12-08 20:18:15 +0000
1244+++ dexter/backend/models.py 2010-12-20 23:23:53 +0000
1245@@ -15,8 +15,6 @@
1246 # with this program. If not, see <http://www.gnu.org/licenses/>.
1247 ### END LICENSE
1248
1249-#dexter_models.py
1250-
1251 """
1252 This module provides the classes that make up the data structures for the
1253 Dexter address book. It utilizes the Storm ORM for interfacing with the db.
1254
1255=== added file 'dexter/backend/serverutils.py'
1256--- dexter/backend/serverutils.py 1970-01-01 00:00:00 +0000
1257+++ dexter/backend/serverutils.py 2010-12-20 23:23:53 +0000
1258@@ -0,0 +1,173 @@
1259+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1260+### BEGIN LICENSE
1261+# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
1262+# This program is free software: you can redistribute it and/or modify it
1263+# under the terms of the GNU General Public License version 3, as published
1264+# by the Free Software Foundation.
1265+#
1266+# This program is distributed in the hope that it will be useful, but
1267+# WITHOUT ANY WARRANTY; without even the implied warranties of
1268+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
1269+# PURPOSE. See the GNU General Public License for more details.
1270+#
1271+# You should have received a copy of the GNU General Public License along
1272+# with this program. If not, see <http://www.gnu.org/licenses/>.
1273+### END LICENSE
1274+
1275+import gio
1276+import datetime
1277+import enchant
1278+import multiprocessing
1279+from Queue import Empty
1280+from storm.expr import Or
1281+import dexter.backend.models as models
1282+
1283+QUEUE = multiprocessing.Queue() # this queue is used for saving photo files in a separate process
1284+
1285+
1286+def create_new_contact(first_name, middle_name=None, last_name=None,
1287+ organization=None, birthday=None, photo_path=None,
1288+ emails=[], phones=[], addresses=[], notes=[], store=None):
1289+ """
1290+ create a new contact in the database, all arguments should be unicode
1291+ strings except for birthday with sould be a datetime.date() object, or
1292+ it can also be a unicode string formatted yyyy-mm-dd. emails and phones
1293+ should be a list of tuples containing the value (address/number) and type.
1294+ addresses should be a list of dictionaries containing the address parts and
1295+ the type. Notes should be a list of unicode strings
1296+ """
1297+ global QUEUE
1298+ if enchant.__ver_minor__ < 5:
1299+ dictionary = False
1300+ else:
1301+ dictionary = True
1302+ if store == None:
1303+ store = models.setup_db(models.DATA_DIR) # create a store if one wasn't provided
1304+ if dictionary:
1305+ try:
1306+ d = enchant.Dict() # for adding names to user spell check dictionary
1307+ dictionary = True
1308+ except enchant.DictNotFoundError:
1309+ dictionary = False
1310+ new = store.add(models.Contact(first_name))
1311+ if dictionary:
1312+ d.add(first_name)
1313+ if middle_name:
1314+ new.middle_name = middle_name
1315+ if dictionary:
1316+ d.add(middle_name)
1317+ if last_name:
1318+ new.last_name = last_name
1319+ if dictionary:
1320+ d.add(last_name)
1321+ new.create_full_name()
1322+ if organization:
1323+ new.organization = organization
1324+ if dictionary:
1325+ d.add(organization)
1326+ if birthday:
1327+ birthdaylist = birthday.split("-")
1328+ new.birthday = datetime.datetime(int(birthdaylist[0]),
1329+ int(birthdaylist[1]),
1330+ int(birthdaylist[2]))
1331+ if photo_path:
1332+ store.flush()
1333+ dest_name = "/" + str(new.id) + str(new.first_name).lower() + \
1334+ str(new.last_name).lower()
1335+ new.photo = unicode(dest_name)
1336+ QUEUE.put([dest_name, photo_path]) # put the photo data into the queue for processing in a separate process
1337+ if not multiprocessing.active_children():
1338+ p = multiprocessing.Process(target=add_photo, args=(QUEUE,))
1339+ p.start() # start the photo adding Process if it isn't already running
1340+ for address, type_ in emails:
1341+ if address != "None":
1342+ email = store.add(models.Email(address, type_))
1343+ new.emails.add(email)
1344+ for number, type_ in phones:
1345+ if number != "None":
1346+ phone = store.add(models.Phone(number, type_))
1347+ new.phones.add(phone)
1348+ for adict in addresses:
1349+ if adict["street"] or adict["city"] or adict["state"] or adict["zip"]:
1350+ address = store.add(models.Address(adict["street"], adict["city"],
1351+ adict["state"], adict["zip"],
1352+ adict["type"]))
1353+ new.addresses.add(address)
1354+ for text in notes:
1355+ if text:
1356+ note = store.add(models.Note(text))
1357+ new.notes.add(note)
1358+ store.flush()
1359+ return new.id
1360+
1361+
1362+def add_photo(q):
1363+ """create a photo for each item in q
1364+
1365+ This is meant to be run in a separate process
1366+ """
1367+ while True:
1368+ try:
1369+ item = q.get(True, 3) # wait for 3 seconds to get an item if q is empty
1370+ dest_name = item[0]
1371+ source_path = item[1]
1372+ dest_path = models.IMAGE_DIR + dest_name
1373+ source = gio.File(source_path)
1374+ dest = gio.File(dest_path)
1375+ source.copy(dest, flags=gio.FILE_COPY_OVERWRITE)
1376+ except Empty:
1377+ break # end the task if the queue is empty for more than 3 seconds
1378+
1379+
1380+def filter_results(text, results, store):
1381+ """
1382+ take a list of contacts and a text string, and return a filtered
1383+ list of contacts containing that string.
1384+
1385+ """
1386+ text = text.lower()
1387+ next_results = []
1388+ for contact in results:
1389+ contact_object = store.find(models.Contact, models.Contact.id == int(contact["id"])).one()
1390+ if unicode(text) in contact["full_name"].lower():
1391+ next_results.append(contact)
1392+ elif contact["organization"] != "None":
1393+ if unicode(text) in contact["organization"].lower():
1394+ next_results.append(contact)
1395+ elif contact_object.emails.count() > 0:
1396+ for email in contact_object.emails:
1397+ if unicode(text) in email.address.lower():
1398+ next_results.append(contact)
1399+ break
1400+ return next_results
1401+
1402+
1403+def search_store(text, store):
1404+ results = []
1405+ search = unicode("%" + text + "%")
1406+ contacts = store.find(models.Contact, Or(models.Contact.full_name.like(search), models.Contact.organization.like(search)))
1407+ emails = store.find(models.Email, models.Email.address.like(search))
1408+ for contact in contacts:
1409+ results.append(contact)
1410+ for email in emails:
1411+ if email.contact not in results:
1412+ results.append(email.contact)
1413+ return results
1414+
1415+def delete_by_id(store, id_number):
1416+ """delete the given contact and it's children from the given store"""
1417+ contact = store.find(models.Contact, models.Contact.id == id_number).one()
1418+ for email in contact.emails:
1419+ store.remove(email)
1420+ for phone in contact.phones:
1421+ store.remove(phone)
1422+ for address in contact.addresses:
1423+ store.remove(address)
1424+ for note in contact.notes:
1425+ store.remove(note)
1426+# try:
1427+# os.remove(models.IMAGE_DIR + contact.photo)
1428+# except:
1429+# pass
1430+ store.remove(contact)
1431+ store.flush()
1432
1433=== modified file 'dexter/backend/utilities.py'
1434--- dexter/backend/utilities.py 2010-12-10 19:33:31 +0000
1435+++ dexter/backend/utilities.py 2010-12-20 23:23:53 +0000
1436@@ -13,23 +13,16 @@
1437 # You should have received a copy of the GNU General Public License along
1438 # with this program. If not, see <http://www.gnu.org/licenses/>.
1439 ### END LICENSE
1440-#dexter_utilities.py
1441
1442 """
1443 This module provides various functions for managing Dexter contacts
1444 """
1445
1446 import os
1447-import sys
1448-import multiprocessing
1449-from Queue import Empty
1450 import datetime
1451-import enchant
1452 import vobject
1453 import base64
1454 import string
1455-import gio
1456-from storm.expr import Or
1457 import threading
1458
1459 import gettext
1460@@ -38,8 +31,6 @@
1461
1462 import dexter.backend.models as models
1463
1464-QUEUE = multiprocessing.Queue() # this queue is used for saving photo files in a separate process
1465-
1466
1467 class ImportThread(threading.Thread):
1468 """a worker thread that parses a vcard string and puts the relevant data in a queue"""
1469@@ -57,52 +48,56 @@
1470 vstring = open(path, 'r').read()
1471 import_from_vcard_string(vstring, self.q)
1472
1473-def export_to_vcard(contacts, path):
1474+def export_to_vcard(contacts, path, dbus):
1475 """export a list of contacts to the vcard file specified by path"""
1476+ GetEmails = dbus.get_dbus_method("GetEmails")
1477+ GetPhones = dbus.get_dbus_method("GetPhones")
1478+ GetAddresses = dbus.get_dbus_method("GetAddresses")
1479+ GetNotes = dbus.get_dbus_method("GetNotes")
1480 card_text =""
1481 for contact in contacts:
1482 file_path = path
1483 card = vobject.vCard()
1484 card.add('n')
1485- card.n.value = vobject.vcard.Name(given=str(contact.first_name))
1486- if contact.last_name:
1487- card.n.value.family = contact.last_name
1488- if contact.middle_name:
1489- card.n.value.additional = contact.middle_name
1490+ card.n.value = vobject.vcard.Name(given=str(contact["first_name"]))
1491+ if contact["last_name"] != "None":
1492+ card.n.value.family = contact["last_name"]
1493+ if contact["middle_name"] != "None":
1494+ card.n.value.additional = contact["middle_name"]
1495 card.add('fn')
1496- card.fn.value = str(contact.full_name)
1497- if contact.organization:
1498+ card.fn.value = str(contact["full_name"])
1499+ if contact["organization"] != "None":
1500 card.add('org')
1501- card.org.value = [str(contact.organization)]
1502- if contact.birthday:
1503+ card.org.value = [str(contact["organization"])]
1504+ if contact["birthday"] != "None":
1505 card.add('bday')
1506- card.bday.value = str(contact.birthday)
1507- for email in contact.emails:
1508+ card.bday.value = str(contact["birthday"])
1509+ for email in GetEmails(int(contact["id"])):
1510 cardemail = card.add('email')
1511- cardemail.value = str(email.address)
1512- cardemail.type_param = str(email.email_type)
1513- for phone in contact.phones:
1514+ cardemail.value = str(email[0])
1515+ cardemail.type_param = str(email[1])
1516+ for phone in GetPhones(int(contact["id"])):
1517 cardphone = card.add('tel')
1518- cardphone.value = str(phone.number)
1519- cardphone.type_param = str(phone.phone_type)
1520- for address in contact.addresses:
1521+ cardphone.value = str(phone[0])
1522+ cardphone.type_param = str(phone[1])
1523+ for address in GetAddresses(int(contact["id"])):
1524 cardadr = card.add('adr')
1525 cardadr.value = vobject.vcard.Address()
1526- if address.street:
1527- cardadr.value.street=address.street
1528- if address.city:
1529- cardadr.value.city=address.city
1530- if address.state:
1531- cardadr.value.region=address.state
1532- if address.zip:
1533- cardadr.value.code=address.zip
1534- cardadr.type_param = str(address.address_type)
1535- for note in contact.notes:
1536+ if address["street"] != "None":
1537+ cardadr.value.street=address["street"]
1538+ if address["city"] != "None":
1539+ cardadr.value.city=address["city"]
1540+ if address["state"] != "None":
1541+ cardadr.value.region=address["state"]
1542+ if address["zip"] != "None":
1543+ cardadr.value.code=address["zip"]
1544+ cardadr.type_param = str(address["type"])
1545+ for note in GetNotes(int(contact["id"])):
1546 cardnote = card.add('note')
1547- cardnote.value = str(note.text)
1548- if contact.photo:
1549+ cardnote.value = str(note)
1550+ if contact["photo"] != "None":
1551 b64 = base64.b64encode(open(models.IMAGE_DIR + \
1552- str(contact.photo), "rb").read())
1553+ str(contact["photo"]), "rb").read())
1554 card.add("photo")
1555 card.photo.value = b64
1556 card.photo.encoding_param = "BASE64"
1557@@ -123,12 +118,12 @@
1558 for card in components:
1559 if q.request_stop:
1560 break
1561- first_name = None
1562- last_name = None
1563- middle_name = None
1564- organization = None
1565- birthday = None
1566- photo_path = None
1567+ first_name = "None"
1568+ last_name = "None"
1569+ middle_name = "None"
1570+ organization = "None"
1571+ birthday = "None"
1572+ photo_path = "None"
1573 notes = []
1574 emails = []
1575 addresses = []
1576@@ -164,9 +159,7 @@
1577 if birthdaylist[2] == u"00":
1578 birthdaylist[2] = u"01"
1579 try:
1580- birthday = datetime.datetime(int(birthdaylist[0]),
1581- int(birthdaylist[1]),
1582- int(birthdaylist[2]))
1583+ birthday = "-".join(birthdaylist)
1584 except: # TODO: replace with proper
1585 print _("Could not import birthday") # error handling and logging
1586 if hasattr(card, 'photo'):
1587@@ -178,7 +171,7 @@
1588 photo_file.write(card.photo.value) # by create_contact later
1589 photo_file.close()
1590 except:
1591- photo_path = None # TODO: replace with proper
1592+ photo_path = "None" # TODO: replace with proper
1593 print _("couldn't add photo") # error handling and logging
1594 if hasattr(card, 'note'):
1595 notes.append(card.note.value.strip())
1596@@ -191,7 +184,7 @@
1597 else:
1598 email_type = child.params[u'TYPE'][0].strip().title()
1599 else:
1600- email_type = None
1601+ email_type = "Home"
1602 emails.append((child.value.strip(), email_type))
1603 if child.name == u'ADR':
1604 if hasattr(child, 'value'):
1605@@ -199,8 +192,8 @@
1606 if hasattr(child, 'type_param'):
1607 adict["type"] = child.type_param.title()
1608 else:
1609- adict["type"] = None
1610- adict["street"] = child.value.street
1611+ adict["type"] = "Home"
1612+ adict["street"] = "".join(child.value.street)
1613 adict["city"] = child.value.city.strip()
1614 adict["state"] = child.value.region.strip()
1615 adict["zip"] = child.value.code.strip()
1616@@ -210,78 +203,84 @@
1617 if hasattr(child, 'type_param'):
1618 phone_type = child.type_param.strip().title()
1619 else:
1620- phone_type = None
1621+ phone_type = "Home"
1622 phones.append((child.value.strip(), phone_type))
1623 q.put([first_name, middle_name, last_name, organization, birthday, photo_path,
1624 emails, phones, addresses, notes], True, 2)
1625
1626
1627-def merge_contacts(contacts, store):
1628+def merge_contacts(contacts, dbus):
1629+ GetEmails = dbus.get_dbus_method("GetEmails")
1630+ GetPhones = dbus.get_dbus_method("GetPhones")
1631+ GetAddresses = dbus.get_dbus_method("GetAddresses")
1632+ GetNotes = dbus.get_dbus_method("GetNotes")
1633+ CreateContact = dbus.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver')
1634+ DeleteContact = dbus.get_dbus_method("DeleteContact")
1635 firstnames = []
1636 middlenames = []
1637 lastnames = []
1638 organizations = []
1639 birthdays = []
1640- photo_path = None
1641+ photo_path = "None"
1642 emails = []
1643 phones = []
1644 addresses = []
1645 note = u""
1646 for contact in contacts:
1647- if contact.first_name:
1648- if not contact.first_name in firstnames:
1649- firstnames.append(contact.first_name)
1650- if contact.middle_name:
1651- if not contact.middle_name in middlenames:
1652- middlenames.append(contact.middle_name)
1653- if contact.last_name:
1654- if not contact.last_name in lastnames:
1655- lastnames.append(contact.last_name)
1656- if contact.organization:
1657- if not contact.organization in organizations:
1658- organizations.append(contact.organization)
1659- if contact.birthday:
1660- if not contact.birthday in birthdays:
1661- birthdays.append(contact.birthday)
1662- if contact.photo:
1663- if not photo_path:
1664- photo_path = models.IMAGE_DIR + contact.photo
1665+ if contact["first_name"] != "None":
1666+ if not contact["first_name"] in firstnames:
1667+ firstnames.append(contact["first_name"])
1668+ if contact["middle_name"] != "None":
1669+ if not contact["middle_name"] in middlenames:
1670+ middlenames.append(contact["middle_name"])
1671+ if contact["last_name"] != "None":
1672+ if not contact["last_name"] in lastnames:
1673+ lastnames.append(contact["last_name"])
1674+ if contact["organization"] != "None":
1675+ if not contact["organization"] in organizations:
1676+ organizations.append(contact["organization"])
1677+ if contact["birthday"] != "None":
1678+ if not contact["birthday"] in birthdays:
1679+ birthdays.append(contact["birthday"])
1680+ if contact["photo"] != "None":
1681+ if photo_path == "None":
1682+ photo_path = models.IMAGE_DIR + contact["photo"]
1683 else:
1684- thissize = os.path.getsize(models.IMAGE_DIR + contact.photo)
1685+ thissize = os.path.getsize(models.IMAGE_DIR + contact["photo"])
1686 oldsize = os.path.getsize(photo_path)
1687 if thissize > oldsize:
1688- photo_path = models.IMAGE_DIR + contact.photo
1689- for email in contact.emails:
1690- emails.append((email.address, email.email_type))
1691- for phone in contact.phones:
1692- phones.append((phone.number, phone.phone_type))
1693- for address in contact.addresses:
1694+ photo_path = models.IMAGE_DIR + contact["photo"]
1695+ for email in GetEmails(int(contact["id"])):
1696+ emails.append((email[0], email[1]))
1697+ for phone in GetPhones(int(contact["id"])):
1698+ phones.append((phone[0], phone[1]))
1699+ for address in GetAddresses(int(contact["id"])):
1700 address_dict = dict()
1701- if address.street:
1702- address_dict["street"] = address.street
1703- else:
1704- address_dict["street"] = None
1705- if address.city:
1706- address_dict["city"] = address.city
1707- else:
1708- address_dict["city"] = None
1709- if address.state:
1710- address_dict["state"] = address.state
1711- else:
1712- address_dict["state"] = None
1713- if address.zip:
1714- address_dict["zip"] = address.zip
1715- else:
1716- address_dict["zip"] = None
1717- if address.address_type:
1718- address_dict["type"] = address.address_type
1719- else:
1720- address_dict["type"] = None
1721+ if address["street"] != "None":
1722+ address_dict["street"] = address["street"]
1723+ else:
1724+ address_dict["street"] = "None"
1725+ if address["city"] != "None":
1726+ address_dict["city"] = address["city"]
1727+ else:
1728+ address_dict["city"] = "None"
1729+ if address["state"] != "None":
1730+ address_dict["state"] = address["state"]
1731+ else:
1732+ address_dict["state"] = "None"
1733+ if address["zip"] != "None":
1734+ address_dict["zip"] = address["zip"]
1735+ else:
1736+ address_dict["zip"] = "None"
1737+ if address["type"] != "None":
1738+ address_dict["type"] = address["type"]
1739+ else:
1740+ address_dict["type"] = "None"
1741 addresses.append(address_dict)
1742- for anote in contact.notes:
1743- if anote.text and anote.text.strip() != "":
1744- note += (anote.text.strip() + "\n")
1745- delete_contact(store, contact)
1746+ for anote in GetNotes(int(contact["id"])):
1747+ if anote and anote.strip() != "":
1748+ note += (anote.strip() + "\n")
1749+ DeleteContact(int(contact["id"]))
1750 if len(firstnames) > 1 or len(middlenames) > 1 or len(lastnames) > 1 or \
1751 len(organizations) > 1 or len(birthdays) > 1:
1752 data = {"first" : firstnames, "middle" : middlenames, "last" : lastnames,
1753@@ -291,161 +290,16 @@
1754 return data
1755 else:
1756 if not firstnames:
1757- firstnames = [None]
1758+ firstnames = ["None"]
1759 if not middlenames:
1760- middlenames = [None]
1761+ middlenames = ["None"]
1762 if not lastnames:
1763- lastnames = [None]
1764+ lastnames = ["None"]
1765 if not organizations:
1766- organizations = [None]
1767+ organizations = ["None"]
1768 if not birthdays:
1769- birthdays = [None]
1770- new_id = create_new_contact(firstnames[0], middlenames[0], lastnames[0],
1771- organizations[0], birthdays[0], photo_path,
1772- emails, phones, addresses, [note], store)
1773+ birthdays = ["None"]
1774+ new_id = CreateContact(firstnames[0], middlenames[0], lastnames[0],
1775+ organizations[0], birthdays[0], photo_path,
1776+ emails, phones, addresses, [note])
1777 return new_id
1778-
1779-
1780-def filter_results(text, results):
1781- """
1782- take a list of contacts and a text string, and return a filtered
1783- list of contacts containing that string.
1784-
1785- """
1786- text = text.lower()
1787- next_results = []
1788- for contact in results:
1789- if unicode(text) in contact.full_name.lower():
1790- next_results.append(contact)
1791- elif contact.organization:
1792- if unicode(text) in contact.organization.lower():
1793- next_results.append(contact)
1794- elif contact.emails.count() > 0:
1795- for email in contact.emails:
1796- if unicode(text) in email.address.lower():
1797- next_results.append(contact)
1798- break
1799- return next_results
1800-
1801-
1802-def search_store(text, store):
1803- results = []
1804- search = unicode("%" + text + "%")
1805- contacts = store.find(models.Contact, Or(models.Contact.full_name.like(search), models.Contact.organization.like(search)))
1806- emails = store.find(models.Email, models.Email.address.like(search))
1807- for contact in contacts:
1808- results.append(contact)
1809- for email in emails:
1810- if email.contact not in results:
1811- results.append(email.contact)
1812- return results
1813-
1814-
1815-def create_new_contact(first_name, middle_name=None, last_name=None, organization=None,
1816- birthday=None, photo_path=None, emails=[],
1817- phones=[], addresses=[], notes=[], store=None):
1818- """
1819- create a new contact in the database, all arguments should be unicode
1820- strings except for birthday with sould be a datetime.date() object, or
1821- it can also be a unicode string formatted yyyy-mm-dd. emails and phones
1822- should be a list of tuples containing the value (address/number) and type.
1823- addresses should be a list of dictionaries containing the address parts and
1824- the type. Notes should be a list of unicode strings
1825- """
1826- global QUEUE
1827- if enchant.__ver_minor__ < 5:
1828- dictionary = False
1829- else:
1830- dictionary = True
1831- if store == None:
1832- store = models.setup_db(models.DATA_DIR) # create a store if one wasn't provided
1833- if dictionary:
1834- try:
1835- d = enchant.Dict() # for adding names to user spell check dictionary
1836- dictionary = True
1837- except enchant.DictNotFoundError:
1838- dictionary = False
1839- new = store.add(models.Contact(first_name))
1840- if dictionary:
1841- d.add(first_name)
1842- if middle_name:
1843- new.middle_name = middle_name
1844- if dictionary:
1845- d.add(middle_name)
1846- if last_name:
1847- new.last_name = last_name
1848- if dictionary:
1849- d.add(last_name)
1850- new.create_full_name()
1851- if organization:
1852- new.organization = organization
1853- if dictionary:
1854- d.add(organization)
1855- if birthday:
1856- new.birthday = birthday
1857- if photo_path:
1858- store.flush()
1859- dest_name = "/" + str(new.id) + str(new.first_name).lower() + \
1860- str(new.last_name).lower()
1861- new.photo = unicode(dest_name)
1862- QUEUE.put([dest_name, photo_path]) # put the photo data into the queue for processing in a separate process
1863- if not multiprocessing.active_children():
1864- p = multiprocessing.Process(target=add_photo, args=(QUEUE,))
1865- p.start() # start the photo adding Process if it isn't already running
1866- for address, type_ in emails:
1867- if address:
1868- email = store.add(models.Email(address, type_))
1869- new.emails.add(email)
1870- for number, type_ in phones:
1871- if number:
1872- phone = store.add(models.Phone(number, type_))
1873- new.phones.add(phone)
1874- for adict in addresses:
1875- if adict["street"] or adict["city"] or adict["state"] or adict["zip"]:
1876- address = store.add(models.Address(adict["street"], adict["city"],
1877- adict["state"], adict["zip"],
1878- adict["type"]))
1879- new.addresses.add(address)
1880- for text in notes:
1881- if text:
1882- note = store.add(models.Note(text))
1883- new.notes.add(note)
1884- store.flush()
1885- return new.id
1886-
1887-
1888-def add_photo(q):
1889- """create a photo for each item in q
1890-
1891- This is meant to be run in a separate process
1892- """
1893- while True:
1894- try:
1895- item = q.get(True, 3) # wait for 3 seconds to get an item if q is empty
1896- dest_name = item[0]
1897- source_path = item[1]
1898- dest_path = models.IMAGE_DIR + dest_name
1899- source = gio.File(source_path)
1900- dest = gio.File(dest_path)
1901- source.copy(dest, flags=gio.FILE_COPY_OVERWRITE)
1902- except Empty:
1903- break # end the task if the queue is empty for more than 3 seconds
1904-
1905-
1906-def delete_contact(store, contact):
1907- """delete the given contact and it's children from the given store"""
1908- for email in contact.emails:
1909- store.remove(email)
1910- for phone in contact.phones:
1911- store.remove(phone)
1912- for address in contact.addresses:
1913- store.remove(address)
1914- for note in contact.notes:
1915- store.remove(note)
1916-# try:
1917-# os.remove(models.IMAGE_DIR + contact.photo)
1918-# except:
1919-# pass
1920- store.remove(contact)
1921- store.flush()
1922-
1923
1924=== modified file 'dexter/enums.py'
1925--- dexter/enums.py 2010-12-08 07:34:05 +0000
1926+++ dexter/enums.py 2010-12-20 23:23:53 +0000
1927@@ -14,4 +14,4 @@
1928 # this program; if not, write to the Free Software Foundation, Inc.,
1929 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1930
1931-(RUN_DAEMON, RUN_CONTACT_DIALOG, RUN_APP) = range(3)
1932+(RUN_CONTACT_DIALOG, RUN_APP) = range(2)
1933
1934=== modified file 'dexter/frontend/dialogs/merge.py'
1935--- dexter/frontend/dialogs/merge.py 2010-12-12 20:24:32 +0000
1936+++ dexter/frontend/dialogs/merge.py 2010-12-20 23:23:53 +0000
1937@@ -71,7 +71,7 @@
1938 self.first_combo.connect("changed", self._on_combo_changed, "first")
1939 self.vbox1.pack_start(self.first_combo, expand=False, fill=True, padding=5)
1940 elif not self.data["first"]:
1941- self.data["first"] = [None]
1942+ self.data["first"] = ["None"]
1943 if len(self.data["middle"]) > 1:
1944 self.middle_combo = gtk.combo_box_new_text()
1945 self.middle_combo.append_text(_("Middle Name... "))
1946@@ -81,7 +81,7 @@
1947 self.middle_combo.connect("changed", self._on_combo_changed, "middle")
1948 self.vbox1.pack_start(self.middle_combo, expand=False, fill=True, padding=5)
1949 elif not self.data["middle"]:
1950- self.data["middle"] = [None]
1951+ self.data["middle"] = ["None"]
1952 if len(self.data["last"]) > 1:
1953 self.last_combo = gtk.combo_box_new_text()
1954 self.last_combo.append_text(_("Last Name... "))
1955@@ -91,7 +91,7 @@
1956 self.last_combo.connect("changed", self._on_combo_changed, "last")
1957 self.vbox1.pack_start(self.last_combo, expand=False, fill=True, padding=5)
1958 elif not self.data["last"]:
1959- self.data["last"] = [None]
1960+ self.data["last"] = ["None"]
1961 if len(self.data["org"]) > 1:
1962 self.org_combo = gtk.combo_box_new_text()
1963 self.org_combo.append_text(_("Organization... "))
1964@@ -101,17 +101,21 @@
1965 self.org_combo.connect("changed", self._on_combo_changed, "org")
1966 self.vbox1.pack_start(self.org_combo, expand=False, fill=True, padding=5)
1967 elif not self.data["org"]:
1968- self.data["org"] = [None]
1969+ self.data["org"] = ["None"]
1970 if len(self.data["birthday"]) > 1:
1971 self.birthday_combo = gtk.combo_box_new_text()
1972 self.birthday_combo.append_text(_("Birthday... "))
1973 for name in self.data["birthday"]:
1974- self.birthday_combo.append_text(name.strftime("%B %d, %Y"))
1975+ birthdaylist = name.split("-")
1976+ birthday = datetime.datetime(int(birthdaylist[0]),
1977+ int(birthdaylist[1]),
1978+ int(birthdaylist[2]))
1979+ self.birthday_combo.append_text(birthday.strftime("%B %d, %Y"))
1980 self.birthday_combo.set_active(0)
1981 self.birthday_combo.connect("changed", self._on_combo_changed, "birthday")
1982 self.vbox1.pack_start(self.birthday_combo, expand=False, fill=True, padding=5)
1983 elif not self.data["birthday"]:
1984- self.data["birthday"] = [None]
1985+ self.data["birthday"] = ["None"]
1986 self.vbox1.show_all()
1987
1988 def all_combos_chosen(self):
1989@@ -126,8 +130,10 @@
1990 def _on_combo_changed(self, widget, param):
1991 if widget.get_active() != 0:
1992 if param == "birthday":
1993- self.data[param] = [datetime.datetime.strptime(widget.get_active_text(),
1994- "%B %d, %Y").date()]
1995+ date = datetime.datetime.strptime(widget.get_active_text(),
1996+ "%B %d, %Y").date()
1997+ datestring = str(date.year) + "-" + str(date.month) + "-" + str(date.day)
1998+ self.data[param] = [datestring]
1999 else:
2000 self.data[param] = [unicode(widget.get_active_text())]
2001 if self.all_combos_chosen():
2002
2003=== modified file 'dexter/frontend/dialogs/new_contact.py'
2004--- dexter/frontend/dialogs/new_contact.py 2010-12-13 19:09:44 +0000
2005+++ dexter/frontend/dialogs/new_contact.py 2010-12-20 23:23:53 +0000
2006@@ -18,10 +18,9 @@
2007 import glib
2008 import gobject
2009 import os
2010-import sys
2011+import dbus
2012 import datetime
2013
2014-from dexter.helpers import get_builder
2015
2016 from dexter.frontend.utils import setup_ui
2017 from dexter.frontend.widgets.glade import DexterContactDialog
2018@@ -45,7 +44,7 @@
2019 self.called_from_dbus = False
2020 self.edit = False
2021 self.new_contact_id = None
2022- self.store = models.setup_db(models.DATA_DIR)
2023+ self._setup_dbus()
2024
2025 self._setup_fields()
2026 self._setup_email()
2027@@ -58,6 +57,17 @@
2028
2029 # Private Functions
2030
2031+ def _setup_dbus(self):
2032+ self.session_bus = dbus.SessionBus()
2033+ self.dbus = self.session_bus.get_object("org.elementary.dexterserver",
2034+ "/org/elementary/dexterserver")
2035+ self.CreateContact = self.dbus.get_dbus_method("CreateContact", dbus_interface='org.elementary.dexterserver')
2036+ self.DeleteContact = self.dbus.get_dbus_method("DeleteContact")
2037+ self.GetEmails = self.dbus.get_dbus_method("GetEmails")
2038+ self.GetPhones = self.dbus.get_dbus_method("GetPhones")
2039+ self.GetAddresses = self.dbus.get_dbus_method("GetAddresses")
2040+ self.GetNotes = self.dbus.get_dbus_method("GetNotes")
2041+
2042 def _setup_fields(self):
2043 self.textbuffer_notes = self.builder.get_object("textbuffer_notes")
2044
2045@@ -166,21 +176,20 @@
2046 return email_hbox
2047
2048 def _new_contact(self):
2049- firstname = None
2050- middlename = None
2051- lastname = None
2052- organization = None
2053+ firstname = "None"
2054+ middlename = "None"
2055+ lastname = "None"
2056+ organization = "None"
2057 emails = []
2058 phones = []
2059 addresses = []
2060- note = None
2061- birthday = None
2062+ note = "None"
2063+ birthday = "None"
2064
2065 # TODO: should we allow contact creation with no first name and
2066 # use email to guess first name?
2067 if not self.firstname_entry.get_text():
2068 return
2069-
2070 if self.firstname_entry.get_text():
2071 firstname = unicode(self.firstname_entry.get_text().strip())
2072 if self.middlename_entry.get_text():
2073@@ -191,17 +200,19 @@
2074 organization = unicode(self.organization_entry.get_text().strip())
2075
2076 try:
2077- month = self.months[self.combobox_month.get_active()]
2078- birthday = datetime.date(int(self.spinbutton_year.get_value()),
2079- month,
2080- int(self.spinbutton_day.get_value()))
2081+ month = self.combobox_month.get_active()+1
2082+ birthday = datetime.date(int(self.spinbutton_year.get_value()),
2083+ month,
2084+ int(self.spinbutton_day.get_value()))
2085 except (KeyError, TypeError, ValueError):
2086 pass
2087 # FIXME: If someone wanted to set a newborn as a contact, this wouldn't work
2088 # Birthday hasn't been modified
2089 if birthday == datetime.date.today():
2090- birthday = None
2091-
2092+ birthday = "None"
2093+ elif birthday != "None":
2094+ birthday = str(int(self.spinbutton_year.get_value())) + "-" + str(month) + "-" + str(int(self.spinbutton_day.get_value()))
2095+
2096 for emailbox in self.email_boxes:
2097 if emailbox.entry.get_text():
2098 email = unicode(emailbox.entry.get_text().strip())
2099@@ -210,8 +221,9 @@
2100 if emailbox.combo.get_active_text():
2101 email_type = unicode(emailbox.combo.get_active_text())
2102 else:
2103- email_type = None
2104- emails.append((email, email_type))
2105+ email_type = "Home"
2106+ if email:
2107+ emails.append((email, email_type))
2108
2109 for phonebox in self.phone_boxes:
2110 if phonebox.entry.get_text():
2111@@ -221,42 +233,42 @@
2112 if phonebox.combo.get_active_text():
2113 phone_type = unicode(phonebox.combo.get_active_text())
2114 else:
2115- phone_type = None
2116- phones.append((phone, phone_type))
2117+ phone_type = "Home"
2118+ if phone:
2119+ phones.append((phone, phone_type))
2120
2121 for addressbox in self.address_boxes:
2122 address_dict = dict()
2123 if addressbox.entry_street.get_text():
2124 address_dict["street"] = unicode(addressbox.entry_street.get_text().strip())
2125 else:
2126- address_dict["street"] = None
2127+ address_dict["street"] = "None"
2128 if addressbox.entry_city.get_text():
2129 address_dict["city"] = unicode(addressbox.entry_city.get_text().strip())
2130 else:
2131- address_dict["city"] = None
2132+ address_dict["city"] = "None"
2133 if addressbox.entry_state.get_text() != addressbox.entry_state.hint_string:
2134 address_dict["state"] = unicode(addressbox.entry_state.get_text().strip())
2135 else:
2136- address_dict["state"] = None
2137+ address_dict["state"] = "None"
2138 if addressbox.entry_zip.get_text() != addressbox.entry_zip.hint_string:
2139 address_dict["zip"] = unicode(addressbox.entry_zip.get_text().strip())
2140 else:
2141- address_dict["zip"] = None
2142+ address_dict["zip"] = "None"
2143 if addressbox.combo.get_active_text():
2144 address_dict["type"] = unicode(addressbox.combo.get_active_text())
2145 else:
2146- address_dict["type"] = None
2147+ address_dict["type"] = "None"
2148 addresses.append((address_dict))
2149 startiter, enditer = self.textbuffer_notes.get_bounds()
2150 if self.textbuffer_notes.get_text(startiter, enditer):
2151 note = unicode(self.textbuffer_notes.get_text(startiter, enditer))
2152-
2153+ if not self.photo_path:
2154+ self.photo_path = "None"
2155 # Add the new contact to the DB and store the ID
2156- self.new_contact_id = utilities.create_new_contact(firstname,
2157- middlename, lastname, organization,
2158- birthday, self.photo_path, emails, phones,
2159- addresses, [note], self.store)
2160- self.store.commit()
2161+ self.new_contact_id = self.CreateContact(firstname, middlename, lastname,
2162+ organization, birthday, self.photo_path,
2163+ emails, phones, addresses, [note])
2164
2165 # Private Callbacks
2166
2167@@ -362,7 +374,7 @@
2168 Called before the dialog returns gtk.RESONSE_OK from run().
2169 """
2170 if self.edit == True:
2171- utilities.delete_contact(self.store, self.contact)
2172+ self.DeleteContact(int(self.contact["id"]))
2173 self._new_contact()
2174 if self.standalone:
2175 gtk.main_quit()
2176@@ -383,77 +395,75 @@
2177
2178 def populate(self):
2179 """populate the various fields, used for editing a contact"""
2180- if self.contact.first_name:
2181- self.firstname_entry.unhint(self.contact.first_name)
2182- if self.contact.middle_name:
2183- self.middlename_entry.unhint(self.contact.middle_name)
2184- if self.contact.last_name:
2185- self.lastname_entry.unhint(self.contact.last_name)
2186- if self.contact.organization:
2187- self.organization_entry.unhint(self.contact.organization)
2188- if self.contact.photo:
2189- self.photo_path = models.IMAGE_DIR + self.contact.photo
2190+ if self.contact["first_name"] != "None":
2191+ self.firstname_entry.unhint(self.contact["first_name"])
2192+ if self.contact["middle_name"] != "None":
2193+ self.middlename_entry.unhint(self.contact["middle_name"])
2194+ if self.contact["last_name"] != "None":
2195+ self.lastname_entry.unhint(self.contact["last_name"])
2196+ if self.contact["organization"] != "None":
2197+ self.organization_entry.unhint(self.contact["organization"])
2198+ if self.contact["photo"] != "None":
2199+ self.photo_path = models.IMAGE_DIR + self.contact["photo"]
2200 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(self.photo_path,
2201 50, 50)
2202 self.image_photo.set_from_pixbuf(pixbuf)
2203- for email in self.contact.emails:
2204+ for email in self.GetEmails(int(self.contact["id"])):
2205 emailbox = self._new_email_field()
2206- if email.address:
2207- emailbox.entry.set_text(email.address)
2208+ if email[0] != "None":
2209+ emailbox.entry.set_text(email[0])
2210 try:
2211- i = self.email_types.index(str(email.email_type))
2212+ i = self.email_types.index(str(email[1]))
2213 emailbox.combo.set_active(i)
2214 except ValueError:
2215 # For custom labels
2216- emailbox.combo.insert_text(0, email.email_type)
2217+ emailbox.combo.insert_text(0, email[1])
2218 emailbox.combo.set_active(0)
2219
2220- for phone in self.contact.phones:
2221+ for phone in self.GetPhones(int(self.contact["id"])):
2222 phonebox = self._new_phone_field()
2223- if phone.number:
2224- phonebox.entry.set_text(phone.number)
2225+ if phone[0] != "None":
2226+ phonebox.entry.set_text(phone[0])
2227 try:
2228- i = self.phone_types.index(str(phone.phone_type))
2229+ i = self.phone_types.index(str(phone[1]))
2230 phonebox.combo.set_active(i)
2231 except ValueError:
2232 # For custom labels
2233- phonebox.combo.insert_text(0, phone.phone_type)
2234+ phonebox.combo.insert_text(0, phone[1])
2235 phonebox.combo.set_active(0)
2236- for address in self.contact.addresses:
2237+ for address in self.GetAddresses(int(self.contact["id"])):
2238 addressbox = self._new_address_field()
2239- if address.street:
2240- addressbox.entry_street.unhint(address.street)
2241- if address.city:
2242- addressbox.entry_city.unhint(address.city)
2243- if address.state:
2244- addressbox.entry_state.unhint(address.state)
2245- if address.zip:
2246- addressbox.entry_zip.unhint(address.zip)
2247- if address.address_type:
2248+ if address["street"] != "None":
2249+ addressbox.entry_street.unhint(address["street"])
2250+ if address["city"] != "None":
2251+ addressbox.entry_city.unhint(address["city"])
2252+ if address["state"] != "None":
2253+ addressbox.entry_state.unhint(address["state"])
2254+ if address["zip"] != "None":
2255+ addressbox.entry_zip.unhint(address["zip"])
2256+ if address["type"] != "None":
2257 try:
2258- i = self.address_types.index(str(address.address_type))
2259+ i = self.address_types.index(str(address["type"]))
2260 addressbox.combo.set_active(i)
2261 # For custom labels
2262 except ValueError:
2263- addressbox.combo.insert_text(0, address.address_type)
2264+ addressbox.combo.insert_text(0, address["type"])
2265 addressbox.combo.set_active(0)
2266- if self.contact.birthday:
2267- self.combobox_month.set_active(self.contact.birthday.month-1)
2268- self.spinbutton_day.set_value(self.contact.birthday.day)
2269- self.spinbutton_year.set_value(self.contact.birthday.year)
2270- if self.contact.notes.one():
2271- self.textbuffer_notes.set_text(self.contact.notes.one().text)
2272+ if self.contact["birthday"] != "None":
2273+ birthdaylist = self.contact["birthday"].split("-")
2274+ self.combobox_month.set_active(int(birthdaylist[1])-1)
2275+ self.spinbutton_day.set_value(int(birthdaylist[2]))
2276+ self.spinbutton_year.set_value(int(birthdaylist[0]))
2277+ note = ""
2278+ for anote in self.GetNotes(int(self.contact["id"])):
2279+ if anote and anote.strip() != "":
2280+ note += (anote.strip() + "\n")
2281+ self.textbuffer_notes.set_text(note)
2282
2283- def set_contact_edit(self, contact, store):
2284- """sets the contact model and edit attribute, gets a store object"""
2285- assert type(contact) == models.Contact
2286+ def set_contact_edit(self, contact):
2287+ """sets the contact model and edit attribute"""
2288 self.contact = contact
2289 self.edit = True
2290- self.store = store
2291-
2292- def set_store(self, store):
2293- """gets a store object"""
2294- self.store = store
2295
2296 def run(self):
2297 self.dialog.show_all()
2298
2299=== modified file 'dexter/frontend/utils.py'
2300--- dexter/frontend/utils.py 2010-12-10 17:59:44 +0000
2301+++ dexter/frontend/utils.py 2010-12-20 23:23:53 +0000
2302@@ -1,18 +1,18 @@
2303-# Authors:
2304-# Andrew Higginson
2305-#
2306-# This program is free software; you can redistribute it and/or modify it under
2307-# the terms of the GNU General Public License as published by the Free Software
2308-# Foundation; version 3.
2309-#
2310-# This program is distributed in the hope that it will be useful, but WITHOUT
2311-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
2312-# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
2313-# details.
2314-#
2315-# You should have received a copy of the GNU General Public License along with
2316-# this program; if not, write to the Free Software Foundation, Inc.,
2317-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
2318+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2319+### BEGIN LICENSE
2320+# Copyright (C) 2010 Allen Lowe <lallenlowe@gmail.com>
2321+# This program is free software: you can redistribute it and/or modify it
2322+# under the terms of the GNU General Public License version 3, as published
2323+# by the Free Software Foundation.
2324+#
2325+# This program is distributed in the hope that it will be useful, but
2326+# WITHOUT ANY WARRANTY; without even the implied warranties of
2327+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2328+# PURPOSE. See the GNU General Public License for more details.
2329+#
2330+# You should have received a copy of the GNU General Public License along
2331+# with this program. If not, see <http://www.gnu.org/licenses/>.
2332+### END LICENSE
2333
2334 import gtk
2335 import os
2336
2337=== modified file 'dexter/frontend/widgets/entries.py'
2338--- dexter/frontend/widgets/entries.py 2010-12-11 19:29:29 +0000
2339+++ dexter/frontend/widgets/entries.py 2010-12-20 23:23:53 +0000
2340@@ -62,7 +62,7 @@
2341
2342 def is_hinted(self):
2343 """check if this entry contains it's hint_string"""
2344- if self.get_text() == self.hint_string:
2345+ if not self.get_text():
2346 return True
2347 else:
2348 return False
2349@@ -91,10 +91,11 @@
2350 """A HintedEntry with the search logic built in"""
2351 __gtype_name__ = "SearchBar"
2352
2353- def __init__(self, hint_string, store):
2354+ def __init__(self, hint_string, dbus):
2355 """create a new SearchBar, data is the list of contacts to search"""
2356 HintedEntry.__init__(self, hint_string)
2357- self.store = store
2358+ self.dbus = dbus
2359+ self.SearchContacts = self.dbus.get_dbus_method("SearchContacts")
2360 self.searching = False
2361 self.previous_search = ""
2362 self.hint()
2363@@ -164,15 +165,7 @@
2364 self.result_set = self.result_sets[text]
2365 else: # else if the string hasn't been searched yet, or the result sets cache has been emptied
2366 # if there is no result set already, search the entire contact list
2367- if not self.result_set:
2368- self.result_set = utilities.search_store(text, self.store)
2369- elif len(text) < 3:
2370- self.result_set = utilities.search_store(text, self.store)
2371- elif self.previous_search in text: # just filter the previous results if the search is incremental
2372- self.result_set = utilities.filter_results(text,
2373- self.result_set)
2374- else:
2375- self.result_set = utilities.search_store(text, self.store)
2376+ self.result_set = self.SearchContacts(text)
2377 # cache the result set for later use
2378 self.result_sets[text] = self.result_set
2379 self.previous_search = text
2380
2381=== removed file 'dexter/signaltest.py'
2382--- dexter/signaltest.py 2010-12-13 20:18:13 +0000
2383+++ dexter/signaltest.py 1970-01-01 00:00:00 +0000
2384@@ -1,15 +0,0 @@
2385-import gtk
2386-import dbus
2387-from dbus.mainloop.glib import DBusGMainLoop
2388-
2389-DBusGMainLoop(set_as_default=True)
2390-
2391-def testing(new_id):
2392- print new_id
2393-
2394-session_bus = dbus.SessionBus()
2395-dexter_object = session_bus.get_object("org.elementary.dexterservice",
2396- "/org/elementary/dexterservice")
2397-dexter_object.connect_to_signal("ContactAdded", testing, 'org.elementary.dexterservice')
2398-
2399-gtk.main()
2400
2401=== added file 'org.elementary.dexterserver.service'
2402--- org.elementary.dexterserver.service 1970-01-01 00:00:00 +0000
2403+++ org.elementary.dexterserver.service 2010-12-20 23:23:53 +0000
2404@@ -0,0 +1,3 @@
2405+[D-BUS Service]
2406+Name=org.elementary.dexterserver
2407+Exec=/usr/bin/dexter-server
2408
2409=== modified file 'po/dexter.pot'
2410--- po/dexter.pot 2010-12-12 20:24:32 +0000
2411+++ po/dexter.pot 2010-12-20 23:23:53 +0000
2412@@ -8,7 +8,7 @@
2413 msgstr ""
2414 "Project-Id-Version: PACKAGE VERSION\n"
2415 "Report-Msgid-Bugs-To: \n"
2416-"POT-Creation-Date: 2010-12-12 13:17-0700\n"
2417+"POT-Creation-Date: 2010-12-20 16:14-0700\n"
2418 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
2419 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
2420 "Language-Team: LANGUAGE <LL@li.org>\n"
2421@@ -161,67 +161,67 @@
2422 msgid "<b>Phones</b>"
2423 msgstr ""
2424
2425-#: ../bin/dexter.py:47
2426+#: ../bin/dexter.py:48
2427 msgid "Show debug messages"
2428 msgstr ""
2429
2430-#: ../bin/dexter.py:50
2431+#: ../bin/dexter.py:51
2432 msgid "Only show the New Contact dialog, then exit"
2433 msgstr ""
2434
2435-#: ../bin/dexter.py:53
2436-msgid "Start Dexter in daemon mode, for use with dbus"
2437-msgstr ""
2438-
2439-#: ../dexter/app.py:149
2440+#: ../dexter/app.py:179
2441 msgid "Type to search..."
2442 msgstr ""
2443
2444-#: ../dexter/app.py:359
2445+#: ../dexter/app.py:223 ../dexter/app.py:586 ../dexter/app.py:632
2446+msgid "Contacts"
2447+msgstr ""
2448+
2449+#: ../dexter/app.py:416
2450 msgid "vCards"
2451 msgstr ""
2452
2453-#: ../dexter/app.py:409
2454+#: ../dexter/app.py:435
2455+msgid "Importing Contacts..."
2456+msgstr ""
2457+
2458+#: ../dexter/app.py:467
2459 msgid "Are you sure you want to delete the selected contact?"
2460 msgstr ""
2461
2462-#: ../dexter/app.py:410
2463+#: ../dexter/app.py:468
2464 msgid "You will not be able to recover this contact at a later date."
2465 msgstr ""
2466
2467-#: ../dexter/app.py:412
2468+#: ../dexter/app.py:470
2469 msgid "Are you sure you want to delete the selected contacts?"
2470 msgstr ""
2471
2472-#: ../dexter/app.py:413
2473+#: ../dexter/app.py:471
2474 msgid "You will not be able to recover these contacts at a later date."
2475 msgstr ""
2476
2477-#: ../dexter/app.py:538 ../dexter/app.py:586
2478-msgid "contacts"
2479-msgstr ""
2480-
2481-#: ../dexter/app.py:547
2482+#: ../dexter/app.py:593
2483 msgid "Could not import vCard file"
2484 msgstr ""
2485
2486-#: ../dexter/app.py:675
2487+#: ../dexter/app.py:736
2488 msgid "Birthdate"
2489 msgstr ""
2490
2491-#: ../dexter/app.py:686
2492+#: ../dexter/app.py:748
2493 msgid "Send email..."
2494 msgstr ""
2495
2496-#: ../dexter/app.py:712
2497+#: ../dexter/app.py:776
2498 msgid "Address"
2499 msgstr ""
2500
2501-#: ../dexter/app.py:738
2502+#: ../dexter/app.py:802
2503 msgid "Search on Google maps..."
2504 msgstr ""
2505
2506-#: ../dexter/app.py:744
2507+#: ../dexter/app.py:809
2508 msgid "Note"
2509 msgstr ""
2510
2511@@ -233,6 +233,16 @@
2512 msgid "translator-credits"
2513 msgstr ""
2514
2515+#. TODO: replace with proper
2516+#: ../dexter/backend/utilities.py:164
2517+msgid "Could not import birthday"
2518+msgstr ""
2519+
2520+#. TODO: replace with proper
2521+#: ../dexter/backend/utilities.py:175
2522+msgid "couldn't add photo"
2523+msgstr ""
2524+
2525 #: ../data/ui/MergeDialog.ui.h:1
2526 msgid " "
2527 msgstr ""
2528@@ -247,16 +257,6 @@
2529 msgid "Please select the correct piece of information below: "
2530 msgstr ""
2531
2532-#. TODO: replace with proper
2533-#: ../dexter/backend/utilities.py:171
2534-msgid "Could not import birthday"
2535-msgstr ""
2536-
2537-#. TODO: replace with proper
2538-#: ../dexter/backend/utilities.py:182
2539-msgid "couldn't add photo"
2540-msgstr ""
2541-
2542 #: ../dexter/frontend/widgets/boxes.py:72
2543 msgid "Street..."
2544 msgstr ""
2545@@ -273,110 +273,110 @@
2546 msgid "ZIP..."
2547 msgstr ""
2548
2549-#: ../dexter/frontend/dialogs/new_contact.py:65
2550+#: ../dexter/frontend/dialogs/new_contact.py:75
2551 msgid "First..."
2552 msgstr ""
2553
2554-#: ../dexter/frontend/dialogs/new_contact.py:68
2555+#: ../dexter/frontend/dialogs/new_contact.py:78
2556 msgid "Middle..."
2557 msgstr ""
2558
2559-#: ../dexter/frontend/dialogs/new_contact.py:70
2560+#: ../dexter/frontend/dialogs/new_contact.py:80
2561 msgid "Last..."
2562 msgstr ""
2563
2564-#: ../dexter/frontend/dialogs/new_contact.py:72
2565+#: ../dexter/frontend/dialogs/new_contact.py:82
2566 msgid "Organization..."
2567 msgstr ""
2568
2569 #. FIXME: Store custom types in database
2570-#: ../dexter/frontend/dialogs/new_contact.py:84
2571-#: ../dexter/frontend/dialogs/new_contact.py:90
2572-#: ../dexter/frontend/dialogs/new_contact.py:95
2573+#: ../dexter/frontend/dialogs/new_contact.py:94
2574+#: ../dexter/frontend/dialogs/new_contact.py:100
2575+#: ../dexter/frontend/dialogs/new_contact.py:105
2576 msgid "Home"
2577 msgstr ""
2578
2579-#: ../dexter/frontend/dialogs/new_contact.py:84
2580-#: ../dexter/frontend/dialogs/new_contact.py:90
2581-#: ../dexter/frontend/dialogs/new_contact.py:95
2582+#: ../dexter/frontend/dialogs/new_contact.py:94
2583+#: ../dexter/frontend/dialogs/new_contact.py:100
2584+#: ../dexter/frontend/dialogs/new_contact.py:105
2585 msgid "Work"
2586 msgstr ""
2587
2588-#: ../dexter/frontend/dialogs/new_contact.py:84
2589+#: ../dexter/frontend/dialogs/new_contact.py:94
2590 msgid "Personal"
2591 msgstr ""
2592
2593-#: ../dexter/frontend/dialogs/new_contact.py:84
2594+#: ../dexter/frontend/dialogs/new_contact.py:94
2595 msgid "School"
2596 msgstr ""
2597
2598-#: ../dexter/frontend/dialogs/new_contact.py:85
2599-#: ../dexter/frontend/dialogs/new_contact.py:90
2600-#: ../dexter/frontend/dialogs/new_contact.py:96
2601+#: ../dexter/frontend/dialogs/new_contact.py:95
2602+#: ../dexter/frontend/dialogs/new_contact.py:100
2603+#: ../dexter/frontend/dialogs/new_contact.py:106
2604 msgid "Other"
2605 msgstr ""
2606
2607-#: ../dexter/frontend/dialogs/new_contact.py:95
2608+#: ../dexter/frontend/dialogs/new_contact.py:105
2609 msgid "Mobile"
2610 msgstr ""
2611
2612-#: ../dexter/frontend/dialogs/new_contact.py:95
2613+#: ../dexter/frontend/dialogs/new_contact.py:105
2614 msgid "Business"
2615 msgstr ""
2616
2617-#: ../dexter/frontend/dialogs/new_contact.py:102
2618+#: ../dexter/frontend/dialogs/new_contact.py:112
2619 msgid "January"
2620 msgstr ""
2621
2622-#: ../dexter/frontend/dialogs/new_contact.py:102
2623+#: ../dexter/frontend/dialogs/new_contact.py:112
2624 msgid "February"
2625 msgstr ""
2626
2627-#: ../dexter/frontend/dialogs/new_contact.py:102
2628+#: ../dexter/frontend/dialogs/new_contact.py:112
2629 msgid "March"
2630 msgstr ""
2631
2632-#: ../dexter/frontend/dialogs/new_contact.py:102
2633+#: ../dexter/frontend/dialogs/new_contact.py:112
2634 msgid "April"
2635 msgstr ""
2636
2637-#: ../dexter/frontend/dialogs/new_contact.py:103
2638+#: ../dexter/frontend/dialogs/new_contact.py:113
2639 msgid "May"
2640 msgstr ""
2641
2642-#: ../dexter/frontend/dialogs/new_contact.py:103
2643+#: ../dexter/frontend/dialogs/new_contact.py:113
2644 msgid "June"
2645 msgstr ""
2646
2647-#: ../dexter/frontend/dialogs/new_contact.py:103
2648+#: ../dexter/frontend/dialogs/new_contact.py:113
2649 msgid "July"
2650 msgstr ""
2651
2652-#: ../dexter/frontend/dialogs/new_contact.py:103
2653+#: ../dexter/frontend/dialogs/new_contact.py:113
2654 msgid "August"
2655 msgstr ""
2656
2657-#: ../dexter/frontend/dialogs/new_contact.py:103
2658+#: ../dexter/frontend/dialogs/new_contact.py:113
2659 msgid "September"
2660 msgstr ""
2661
2662-#: ../dexter/frontend/dialogs/new_contact.py:104
2663+#: ../dexter/frontend/dialogs/new_contact.py:114
2664 msgid "October"
2665 msgstr ""
2666
2667-#: ../dexter/frontend/dialogs/new_contact.py:104
2668+#: ../dexter/frontend/dialogs/new_contact.py:114
2669 msgid "November"
2670 msgstr ""
2671
2672-#: ../dexter/frontend/dialogs/new_contact.py:104
2673+#: ../dexter/frontend/dialogs/new_contact.py:114
2674 msgid "December"
2675 msgstr ""
2676
2677-#: ../dexter/frontend/dialogs/new_contact.py:141
2678+#: ../dexter/frontend/dialogs/new_contact.py:151
2679 msgid "Number:"
2680 msgstr ""
2681
2682-#: ../dexter/frontend/dialogs/new_contact.py:155
2683+#: ../dexter/frontend/dialogs/new_contact.py:165
2684 msgid "Address:"
2685 msgstr ""
2686
2687
2688=== modified file 'setup.py'
2689--- setup.py 2010-12-10 03:12:04 +0000
2690+++ setup.py 2010-12-20 23:23:53 +0000
2691@@ -95,6 +95,7 @@
2692 description='Address Book that goes hand in hand with Postler',
2693 long_description='Dexter is a very simple, easy to use address book, designed with the home user in mind. This personal contact manager integrates with Postler, and can import and export contacts in vCard format.',
2694 url='https://launchpad.net/dexter-rolodex',
2695+ data_files=[('share/dbus-1/services/', ['org.elementary.dexterserver.service'])],
2696 cmdclass={'install': InstallAndUpdateDataDirectory}
2697 )
2698

Subscribers

People subscribed via source and target branches