~ubuntu-branches/debian/sid/docky/sid

« back to all changes in this revision

Viewing changes to scripts/zeitgeist_journal.py

  • Committer: Package Import Robot
  • Author(s): Rico Tzschichholz
  • Date: 2012-01-19 19:03:38 UTC
  • mfrom: (1.1.14) (10.1.9 experimental)
  • Revision ID: package-import@ubuntu.com-20120119190338-n44q7tmqsrkudvk7
Tags: 2.1.3-2
* Upload to unstable
* debian/watch:
  + Look for xz tarballs from now on

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
# -.- coding: utf-8 -.-
3
 
 
4
 
# Zeitgeist
5
 
#
6
 
# Copyright © 2009 Seif Lotfy <seif@lotfy.com>
7
 
# Copyright © 2009 Siegfried Gevatter <siegfried@gevatter.com>
8
 
#
9
 
# This program is free software: you can redistribute it and/or modify
10
 
# it under the terms of the GNU General Public License as published by
11
 
# the Free Software Foundation, either version 3 of the License, or
12
 
# (at your option) any later version.
13
 
#
14
 
# This program is distributed in the hope that it will be useful,
15
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 
# GNU Lesser General Public License for more details.
18
 
#
19
 
# You should have received a copy of the GNU Lesser General Public License
20
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 
 
22
 
import os.path
23
 
import gobject
24
 
import sys
25
 
import urllib
26
 
import datetime
27
 
import time
28
 
import os
29
 
import gtk
30
 
import gnome.ui
31
 
import atexit
32
 
 
33
 
try:
34
 
        from docky.docky import DockyItem, DockySink
35
 
        from zeitgeist.client import ZeitgeistClient
36
 
        from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, StorageState
37
 
        from signal import signal, SIGTERM
38
 
        from sys import exit
39
 
except ImportError, e:
40
 
        exit()
41
 
 
42
 
try:
43
 
        CLIENT = ZeitgeistClient()
44
 
        version = [int(x) for x in CLIENT.get_version()]
45
 
        MIN_VERSION = [0, 3, 1, 0]
46
 
        if version < MIN_VERSION:
47
 
                print "PLEASE USE ZEITGEIST 0.3.1 or above"
48
 
                exit()
49
 
 
50
 
except RuntimeError, e:
51
 
        print "Unable to connect to Zeitgeist, won't send events. Reason: '%s'" %e
52
 
        exit()
53
 
 
54
 
INTERPRETATION = {
55
 
                "http://zeitgeist-project.com/schema/1.0/core#VisitEvent":"OPENED",
56
 
                "http://zeitgeist-project.com/schema/1.0/core#ModifyEvent":"SAVED",
57
 
                "http://zeitgeist-project.com/schema/1.0/core#CreateEvent":"CREATED"
58
 
                }
59
 
 
60
 
class LaunchManager:
61
 
    """
62
 
    A program lauching utility which handles opening a URI or executing a
63
 
    program or .desktop launcher, handling variable expansion in the Exec
64
 
    string.
65
 
 
66
 
    Adds the launched URI or launcher to the ~/.recently-used log. Sets a
67
 
    DESKTOP_STARTUP_ID environment variable containing useful information such
68
 
    as the URI which caused the program execution and a timestamp.
69
 
 
70
 
    See the startup notification spec for more information on
71
 
    DESKTOP_STARTUP_IDs.
72
 
    """
73
 
    
74
 
    def __init__(self):
75
 
        self.recent_model = None
76
 
 
77
 
    def _get_recent_model(self):
78
 
        # FIXME: This avoids import cycles
79
 
        if not self.recent_model:
80
 
            import zeitgeist_recent
81
 
            self.recent_model = zeitgeist_recent.recent_model
82
 
        return self.recent_model
83
 
    
84
 
    def launch_uri(self, uri, mimetype = None):
85
 
        assert uri, "Must specify URI to launch"
86
 
    
87
 
        child = os.fork()
88
 
        if not child:
89
 
            # Inside forked child
90
 
            os.setsid()
91
 
            os.environ['zeitgeist_LAUNCHER'] = uri
92
 
            os.environ['DESKTOP_STARTUP_ID'] = self.make_startup_id(uri)
93
 
            os.spawnlp(os.P_NOWAIT, "gnome-open", "gnome-open", uri)
94
 
            os._exit(0)
95
 
        else:
96
 
            os.wait()
97
 
            if not mimetype:
98
 
                mimetype = "application/octet-stream"
99
 
            try:
100
 
                # Use XDG to lookup mime type based on file name.
101
 
                # gtk_recent_manager_add_full requires it.
102
 
                import xdg.Mime
103
 
                mimetype = xdg.Mime.get_type_by_name(uri)
104
 
                if mimetype:
105
 
                    mimetype = str(mimetype)
106
 
                return mimetype
107
 
            except (ImportError, NameError):
108
 
                # No mimetype found for URI: %s
109
 
                pass
110
 
        return child
111
 
    
112
 
    def get_local_path(self, uri):
113
 
        scheme, path = urllib.splittype(uri)
114
 
        if scheme == None:
115
 
            return uri
116
 
        elif scheme == "file":
117
 
            path = urllib.url2pathname(path)
118
 
            if path[:3] == "///":
119
 
                path = path[2:]
120
 
            return path
121
 
        return None
122
 
    
123
 
    def launch_command_with_uris(self, command, uri_list, launcher_uri = None):
124
 
        if command.rfind("%U") > -1:
125
 
            uri_str = ""
126
 
            for uri in uri_list:
127
 
                uri_str = uri_str + " " + uri
128
 
                return self.launch_command(command.replace("%U", uri_str), launcher_uri)
129
 
        elif command.rfind("%F") > -1:
130
 
            file_str = ""
131
 
            for uri in uri_list:
132
 
                uri = self.get_local_path(self, uri)
133
 
                if uri:
134
 
                    file_str = file_str + " " + uri
135
 
                else:
136
 
                    # Command does not support non-file URLs
137
 
                    pass
138
 
            return self.launch_command(command.replace("%F", file_str), launcher_uri)
139
 
        elif command.rfind("%u") > -1:
140
 
            startup_ids = []
141
 
            for uri in uri_list:
142
 
                startup_ids.append(self.launch_command(command.replace("%u", uri), launcher_uri))
143
 
            else:
144
 
                return self.launch_command(command.replace("%u", ""), launcher_uri)
145
 
            return startup_ids
146
 
        elif command.rfind("%f") > -1:
147
 
            startup_ids = []
148
 
            for uri in uri_list:
149
 
                uri = self.get_local_path(self, uri)
150
 
                if uri:
151
 
                    startup_ids.append(self.launch_command(command.replace("%f", uri),
152
 
                                                           launcher_uri))
153
 
                else:
154
 
                    #print " !!! Command does not support non-file URLs: ", command
155
 
                    pass
156
 
            else:
157
 
                return self.launch_command(command.replace("%f", ""), launcher_uri)
158
 
            return startup_ids
159
 
        else:
160
 
            return self.launch_command(command, launcher_uri)
161
 
    
162
 
    def make_startup_id(self, key, ev_time = None):
163
 
        if not ev_time:
164
 
            ev_time = gtk.get_current_event_time()
165
 
        if not key:
166
 
            return "zeitgeist_TIME%d" % ev_time
167
 
        else:
168
 
            return "zeitgeist:%s_TIME%d" % (key, ev_time)
169
 
 
170
 
    def parse_startup_id(self, id):
171
 
        if id and id.startswith("zeitgeist:"):
172
 
            try:
173
 
                uri = id[len("zeitgeist:"):id.rfind("_TIME")]
174
 
                timestamp = id[id.rfind("_TIME") + len("_TIME"):]
175
 
                return (uri, timestamp)
176
 
            except IndexError:
177
 
                pass
178
 
        return (None, None)
179
 
 
180
 
    def launch_command(self, command, launcher_uri = None):
181
 
        startup_id = self.make_startup_id(launcher_uri)
182
 
        child = os.fork()
183
 
        if not child:
184
 
            # Inside forked child
185
 
            os.setsid()
186
 
            os.environ['DESKTOP_STARTUP_ID'] = startup_id
187
 
            if launcher_uri:
188
 
                os.environ['zeitgeist_LAUNCHER'] = launcher_uri
189
 
            os.system(command)
190
 
            os._exit(0)
191
 
        else:
192
 
            os.wait()
193
 
           
194
 
            return (child, startup_id)
195
 
 
196
 
class IconFactory():
197
 
    """
198
 
    Icon lookup swiss-army knife (from menutreemodel.py)
199
 
    """
200
 
    
201
 
    def __init__(self):
202
 
        self.icon_dict={}
203
 
    
204
 
    def load_icon_from_path(self, icon_path, icon_size=None):
205
 
        try:
206
 
            if icon_size:
207
 
                icon = gtk.gdk.pixbuf_new_from_file_at_size(icon_path,
208
 
                    int(icon_size), int(icon_size))
209
 
                return icon
210
 
            else:
211
 
                return gtk.gdk.pixbuf_new_from_file(icon_path)
212
 
        except Exception:
213
 
            pass
214
 
        return None
215
 
    
216
 
    def load_icon_from_data_dirs(self, icon_value, icon_size=None):
217
 
        data_dirs = None
218
 
        if os.environ.has_key("XDG_DATA_DIRS"):
219
 
            data_dirs = os.environ["XDG_DATA_DIRS"]
220
 
        if not data_dirs:
221
 
            data_dirs = "/usr/local/share/:/usr/share/"
222
 
 
223
 
        for data_dir in data_dirs.split(":"):
224
 
            retval = self.load_icon_from_path(os.path.join(data_dir, "pixmaps", icon_value),
225
 
                                              icon_size)
226
 
            if retval:
227
 
                return retval
228
 
            
229
 
            retval = self.load_icon_from_path(os.path.join(data_dir, "icons", icon_value),
230
 
                                              icon_size)
231
 
            if retval:
232
 
                return retval
233
 
        return None
234
 
    
235
 
    def transparentize(self, pixbuf, percent):
236
 
        pixbuf = pixbuf.add_alpha(False, '0', '0', '0')
237
 
        for row in pixbuf.get_pixels_array():
238
 
            for pix in row:
239
 
                pix[3] = min(int(pix[3]), 255 - (percent * 0.01 * 255))
240
 
        return pixbuf
241
 
    
242
 
    def greyscale(self, pixbuf):
243
 
        pixbuf = pixbuf.copy()
244
 
        for row in pixbuf.get_pixels_array():
245
 
            for pix in row:
246
 
                pix[0] = pix[1] = pix[2] = (int(pix[0]) + int(pix[1]) + int(pix[2])) / 3
247
 
        return pixbuf
248
 
    
249
 
    def load_icon(self, icon_value, icon_size, force_size=True, cache=True):
250
 
        if not self.icon_dict.has_key(str(icon_value)+str(icon_size)) or not cache:
251
 
            try:
252
 
                if isinstance(icon_value, gtk.gdk.Pixbuf):
253
 
                    return icon_value
254
 
                elif os.path.isabs(icon_value):
255
 
                    icon = self.load_icon_from_path(icon_value, icon_size)
256
 
                    if icon:
257
 
                        return icon
258
 
                    icon_name = os.path.basename(icon_value)
259
 
                else:
260
 
                    icon_name = icon_value
261
 
                
262
 
                if icon_name.endswith(".png"):
263
 
                    icon_name = icon_name[:-len(".png")]
264
 
                elif icon_name.endswith(".xpm"):
265
 
                    icon_name = icon_name[:-len(".xpm")]
266
 
                elif icon_name.endswith(".svg"):
267
 
                    icon_name = icon_name[:-len(".svg")]
268
 
                
269
 
                icon = None
270
 
                info = icon_theme.lookup_icon(icon_name, icon_size, gtk.ICON_LOOKUP_USE_BUILTIN)
271
 
                if info:
272
 
                    if icon_name.startswith("gtk-"):
273
 
                        icon = info.load_icon()
274
 
                    elif info.get_filename():
275
 
                        icon = self.load_icon_from_path(info.get_filename(), icon_size)
276
 
                else:
277
 
                    icon = self.load_icon_from_data_dirs(icon_value, icon_size) 
278
 
                
279
 
                if cache:
280
 
                    self.icon_dict[str(icon_value)+str(icon_size)] = icon
281
 
                return icon
282
 
            except Exception:
283
 
                self.icon_dict[str(icon_value)+str(icon_size)] = None
284
 
                return None
285
 
        else:
286
 
            return self.icon_dict[str(icon_value)+str(icon_size)]
287
 
    
288
 
    def load_image(self, icon_value, icon_size, force_size=True):
289
 
        pixbuf = self.load_icon(icon_value, icon_size, force_size)
290
 
        img = gtk.Image()
291
 
        img.set_from_pixbuf(pixbuf)
292
 
        img.show()
293
 
        return img
294
 
 
295
 
    def make_icon_frame(self, thumb, icon_size = None, blend = False):
296
 
        border = 1
297
 
 
298
 
        mythumb = gtk.gdk.Pixbuf(thumb.get_colorspace(),
299
 
                                 True,
300
 
                                 thumb.get_bits_per_sample(),
301
 
                                 thumb.get_width(),
302
 
                                 thumb.get_height())
303
 
        mythumb.fill(0x00000080) # black, 50% transparent
304
 
        if blend:
305
 
            thumb.composite(mythumb, 0.5, 0.5,
306
 
                            thumb.get_width(), thumb.get_height(),
307
 
                            0.5, 0.5,
308
 
                            0.5, 0.5,
309
 
                            gtk.gdk.INTERP_NEAREST,
310
 
                            256)
311
 
        thumb.copy_area(border, border,
312
 
                        thumb.get_width() - (border * 2), thumb.get_height() - (border * 2),
313
 
                        mythumb,
314
 
                        border, border)
315
 
        return mythumb
316
 
    
317
 
    
318
 
class Thumbnailer:
319
 
    
320
 
    def __init__(self):
321
 
        self.icon_dict={}
322
 
 
323
 
    def get_icon(self, subject, icon_size, timestamp = 0):
324
 
        
325
 
        uri = subject.uri
326
 
        if not self.icon_dict.get(uri+str(icon_size)):
327
 
            cached_icon = self._lookup_or_make_thumb(uri, subject.mimetype,
328
 
                icon_size, timestamp)
329
 
            self.icon_dict[uri+str(icon_size)] = cached_icon
330
 
        
331
 
        return self.icon_dict[uri+str(icon_size)]
332
 
 
333
 
    def _lookup_or_make_thumb(self, uri, mimetype, icon_size, timestamp):
334
 
        icon_name, icon_type = \
335
 
                   gnome.ui.icon_lookup(icon_theme, thumb_factory, uri, mimetype, 0)
336
 
        try:
337
 
            if icon_type == gnome.ui.ICON_LOOKUP_RESULT_FLAGS_THUMBNAIL or \
338
 
                   thumb_factory.has_valid_failed_thumbnail(uri,int(timestamp)):
339
 
                # Use existing thumbnail
340
 
                thumb = icon_factory.load_icon(icon_name, icon_size)
341
 
            elif self._is_local_uri(uri):
342
 
                # Generate a thumbnail for local files only
343
 
                thumb = thumb_factory.generate_thumbnail(uri, mimetype)
344
 
                thumb_factory.save_thumbnail(thumb, uri, timestamp)
345
 
 
346
 
            if thumb:
347
 
                thumb = icon_factory.make_icon_frame(thumb, icon_size)
348
 
                return thumb
349
 
            
350
 
        except Exception:
351
 
            pass
352
 
 
353
 
        return icon_factory.load_icon(icon_name, icon_size)
354
 
 
355
 
    def _is_local_uri(self, uri):
356
 
        # NOTE: gnomevfs.URI.is_local seems to hang for some URIs (e.g. ssh
357
 
        #        or http).  So look in a list of local schemes which comes
358
 
        #        directly from gnome_vfs_uri_is_local_scheme.
359
 
        scheme, path = urllib.splittype(self.get_uri() or "")
360
 
        return not scheme or scheme in ("file", "help", "ghelp", "gnome-help", "trash",
361
 
                                        "man", "info", "hardware", "search", "pipe",
362
 
                                        "gnome-trash")
363
 
 
364
 
class CellRendererPixbuf(gtk.CellRendererPixbuf):
365
 
    
366
 
    __gsignals__ = {
367
 
        'toggled': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
368
 
        (gobject.TYPE_STRING,))
369
 
    }
370
 
    def __init__(self):
371
 
        gtk.CellRendererPixbuf.__init__(self)
372
 
        self.set_property('mode', gtk.CELL_RENDERER_MODE_ACTIVATABLE)
373
 
    
374
 
    def do_activate(self, event, widget, path, background_area, cell_area, flags):
375
 
        model = widget.get_model()
376
 
        self.emit("toggled",path)
377
 
 
378
 
icon_theme = gtk.icon_theme_get_default()
379
 
icon_factory = IconFactory()
380
 
thumbnailer = Thumbnailer()
381
 
thumb_factory = gnome.ui.ThumbnailFactory("normal")
382
 
launcher = LaunchManager()
383
 
 
384
 
 
385
 
from zeitgeist.datamodel import Event, Subject, Interpretation, Manifestation, StorageState
386
 
 
387
 
class Window(gtk.Window):
388
 
        def __init__(self, CLIENT):
389
 
                gtk.Window.__init__(self)
390
 
                self.view = DataIconView()
391
 
                scroll = gtk.ScrolledWindow()
392
 
                scroll.add_with_viewport(self.view)
393
 
                self.add(scroll)
394
 
                scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
395
 
                self._zg = CLIENT
396
 
                self.set_size_request(600,480)
397
 
                self.show_all()
398
 
 
399
 
        def load_events(self, start=0, end=0, actor=None):
400
 
                self.set_title("Journal for "+actor)
401
 
                def exists(uri):
402
 
                        return not uri.startswith("file://") or os.path.exists(urllib.unquote(str(uri[7:])))
403
 
 
404
 
                def _handle_find_events(ids):
405
 
                        self._zg.get_events(ids, _handle_get_events)
406
 
 
407
 
                def _handle_get_events(events):
408
 
                        uris = []
409
 
                        for event in events:
410
 
                                for subject in event.subjects:
411
 
                                        if exists(subject.uri):
412
 
                                                self.view.append_object(event)
413
 
                
414
 
                event = Event()
415
 
                event.set_actor(actor)
416
 
                self._zg.find_events_for_templates([event],_handle_get_events, [start, end], StorageState.Any, 0, 4) 
417
 
 
418
 
 
419
 
class DataIconView(gtk.TreeView):
420
 
        def __init__(self):
421
 
                gtk.TreeView.__init__(self)
422
 
                self.store = gtk.TreeStore(gtk.gdk.Pixbuf,
423
 
                                        str,    #TIME
424
 
                                        str,    #INTERPRETATION
425
 
                                        str,    #Subject_TEXT
426
 
                                        gobject.TYPE_PYOBJECT
427
 
                                        )
428
 
 
429
 
                # Icon 
430
 
                icon_cell = gtk.CellRendererPixbuf()
431
 
                icon_cell.set_property("yalign", 0.5)
432
 
                icon_column = gtk.TreeViewColumn("Icon",icon_cell,pixbuf=0)
433
 
                
434
 
                time_cell = gtk.CellRendererText()
435
 
                time_column = gtk.TreeViewColumn("Time", time_cell, markup=1)
436
 
                                
437
 
                interp_cell = gtk.CellRendererText()
438
 
                interp_column = gtk.TreeViewColumn("Interpretation", interp_cell, markup=2)
439
 
                
440
 
                subject_cell = gtk.CellRendererText()
441
 
                subject_column = gtk.TreeViewColumn("Subject", subject_cell, markup=3)
442
 
                
443
 
                self.append_column(time_column)
444
 
                self.append_column(icon_column)
445
 
                self.append_column(subject_column)
446
 
                self.append_column(interp_column)
447
 
                self.set_model(self.store)
448
 
 
449
 
                self.set_headers_visible(False)
450
 
                self.iter = {"uri":None, "iter":None}
451
 
 
452
 
        def clear(self):
453
 
                self.store.clear()
454
 
 
455
 
        def append_object(self, event):
456
 
 
457
 
                icon = thumbnailer.get_icon(event.subjects[0], 32)
458
 
                date = int(event.timestamp)/1000
459
 
                date = datetime.datetime.fromtimestamp(date).strftime("%H:%M:%S         %A, %D") 
460
 
 
461
 
                iterator = None
462
 
                if self.iter["uri"] == event.subjects[0].uri:
463
 
                        self.store.append(self.iter["iter"], 
464
 
                                                [icon,
465
 
                                                date,
466
 
                                                INTERPRETATION[event.interpretation],
467
 
                                                event.subjects[0].text,
468
 
                                                event])
469
 
                else:
470
 
                        iterator = None
471
 
                        self.iter["uri"] = event.subjects[0].uri
472
 
                        self.iter["iter"] = self.store.append(None, [icon,
473
 
                                                                date,
474
 
                                                                INTERPRETATION[event.interpretation],
475
 
                                                                event.subjects[0].text,
476
 
                                                                event])
477
 
 
478
 
class DockyJournalItem(DockyItem):
479
 
        def __init__(self, path):
480
 
                DockyItem.__init__(self, path)
481
 
                menu_id = self.iface.AddMenuItem("Journal", "document-open-recent", "")
482
 
                self.id_map[menu_id] = "Journal"
483
 
                self.uri = ""
484
 
                if self.iface.GetOwnsUri():
485
 
                        self.uri = self.iface.GetUri()
486
 
                else:
487
 
                        self.uri = self.iface.GetDesktopFile()
488
 
 
489
 
        def menu_pressed(self, menu_id):
490
 
                if self.id_map[menu_id] == "Journal":
491
 
                        window = Window(CLIENT)
492
 
                        window.load_events(0, time.time() * 1000, self.uri)
493
 
                        
494
 
class DockyJournalSink(DockySink):
495
 
        def item_path_found(self, pathtoitem, item):
496
 
                if item.GetOwnsUri() or item.GetOwnsDesktopFile():
497
 
                        self.items[pathtoitem] = DockyJournalItem(pathtoitem)
498
 
 
499
 
dockysink = DockyJournalSink()
500
 
 
501
 
def cleanup ():
502
 
        dockysink.dispose ()
503
 
 
504
 
if __name__ == "__main__":
505
 
        mainloop = gobject.MainLoop(is_running=True)
506
 
 
507
 
        atexit.register (cleanup)
508
 
        
509
 
        signal(SIGTERM, lambda signum, stack_frame: exit(1))
510
 
 
511
 
        while mainloop.is_running():
512
 
            mainloop.run()