~khurshid-alam/gwibber/gwibber-hack

« back to all changes in this revision

Viewing changes to gwibber/microblog/dispatcher.py

  • Committer: Khurshid Alam
  • Date: 2012-04-06 14:38:38 UTC
  • Revision ID: khurshid.alam@linuxmail.org-20120406143838-nz7hjg8vtzi2wl7i
initial revision

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python
 
2
# -*- coding: utf-8 -*-
 
3
 
 
4
import multiprocessing, threading, traceback, json
 
5
import gobject, dbus, dbus.service
 
6
import sqlite3, mx.DateTime, re, uuid
 
7
import urlshorter, storage, network, util, config
 
8
from gettext import lgettext as _
 
9
import signal
 
10
 
 
11
from util import log
 
12
from util import resources
 
13
from util import exceptions
 
14
from util.const import *
 
15
import subprocess
 
16
 
 
17
try:
 
18
  from gi.repository import Unity, Dbusmenu
 
19
except:
 
20
  Unity = None
 
21
  Dbusmenu = None
 
22
 
 
23
# Try to import * from custom, install custom.py to include packaging 
 
24
# customizations like distro API keys, etc
 
25
try:
 
26
  from util.custom import *
 
27
except:
 
28
  pass
 
29
 
 
30
try:
 
31
  import indicate
 
32
except:
 
33
  indicate = None
 
34
 
 
35
gobject.threads_init()
 
36
 
 
37
log.logger.name = "Gwibber Dispatcher"
 
38
 
 
39
# Dynamically build a list of available service plugins
 
40
PROTOCOLS = {}
 
41
for p in util.resources.get_plugin_dirs()[0]:
 
42
    PROTOCOLS[str(p)] = __import__("%s" % p, fromlist='*')
 
43
    print "Loading plugin %s version %s" % (PROTOCOLS[str(p)].PROTOCOL_INFO["name"], PROTOCOLS[str(p)].PROTOCOL_INFO["version"])
 
44
    #print "Path %s" % str(PROTOCOLS[str(p)].__file__)
 
45
    # FIXME: Figure out why the logger doesn't log here
 
46
    #log.logger.info("Loading plugin for %s", p)
 
47
 
 
48
FEATURES = json.loads(GWIBBER_OPERATIONS)
 
49
SERVICES = dict([(k, v.PROTOCOL_INFO) for k, v in PROTOCOLS.items()])
 
50
SETTINGS = config.Preferences()
 
51
 
 
52
if SETTINGS["interval"] < 5:
 
53
  #log.logger.info( "Migrating refresh interval from %d to %d", SETTINGS["interval"], 5)
 
54
  SETTINGS["interval"] = 5
 
55
 
 
56
 
 
57
def perform_operation((account, opname, args, transient)):
 
58
  try:
 
59
    stream = FEATURES[opname]["stream"] or opname
 
60
    logtext = "<%s:%s>" % (account["service"], opname)
 
61
 
 
62
    logtext = "<%s:%s>" % (account["service"], opname)
 
63
    log.logger.debug("%s Performing operation", logtext)
 
64
 
 
65
    args = dict((str(k), v) for k, v in args.items())
 
66
    message_data = PROTOCOLS[account["service"]].Client(account)(opname, **args)
 
67
    text_cleaner = re.compile(u"[: \n\t\r♻♺]+|@[^ ]+|![^ ]+|#[^ ]+") # signs, @nickname, !group, #tag
 
68
    new_messages = []
 
69
 
 
70
    if message_data is not None:
 
71
      for m in message_data:
 
72
        try: 
 
73
          if isinstance(m, dict) and m.has_key("mid"):
 
74
            m["id"] = uuid.uuid1().hex
 
75
            m["operation"] = opname
 
76
            m["stream"] = stream
 
77
            m["transient"] = transient
 
78
            m["rtl"] = util.isRTL(re.sub(text_cleaner, "", m["text"].decode('utf-8')))
 
79
            if m.has_key("type"):
 
80
              if m["type"] == "link": m["stream"] = "links"
 
81
              if m["type"] == "video": m["stream"] = "videos"
 
82
              if m["type"] == "photo": m["stream"] = "images"
 
83
 
 
84
            log.logger.debug("%s Adding record", logtext)
 
85
 
 
86
            new_messages.insert(0, (
 
87
              m["id"],
 
88
              m["mid"],
 
89
              m["account"],
 
90
              account["service"],
 
91
              opname,
 
92
              transient,
 
93
              m["stream"] or stream,
 
94
              m["time"],
 
95
              m["text"],
 
96
              m.get("sender", {}).get("is_me", None), 
 
97
              m.get("to_me", None),
 
98
              m.get("sender", {}).get("nick", None),
 
99
              m.get("reply", {}).get("nick", None),
 
100
              json.dumps(m)
 
101
            ))
 
102
          elif isinstance(m, dict) and m.has_key("error"):
 
103
            new_messages.insert(0, (
 
104
              "error",
 
105
              json.dumps(m)
 
106
            ))
 
107
        except Exception as e:
 
108
          if not "logtext" in locals(): logtext = "<UNKNOWN>"
 
109
          log.logger.error("%s Operation failed", logtext)
 
110
 
 
111
    log.logger.debug("%s Finished operation", logtext)
 
112
    return ("Success", new_messages)
 
113
  except Exception as e:
 
114
    if not "logtext" in locals(): logtext = "<UNKNOWN>"
 
115
    log.logger.error("%s Operation failed", logtext)
 
116
    log.logger.debug("Traceback:\n%s", traceback.format_exc())
 
117
    return ("Failure", traceback.format_exc())
 
118
 
 
119
class OperationCollector:
 
120
  def __init__(self, dispatcher):
 
121
    self.dispatcher = dispatcher
 
122
 
 
123
  def get_passwords(self, acct):
 
124
    for key, val in acct.items():
 
125
      if hasattr(val, "startswith") and val.startswith(":KEYRING:"):
 
126
        id = "%s/%s" % (acct["id"], key)
 
127
        try:
 
128
          acct[key] = self.dispatcher.accounts.passwords[id]
 
129
        except:
 
130
          pass
 
131
    return acct 
 
132
 
 
133
  def get_accounts(self):
 
134
    data = json.loads(self.dispatcher.accounts.List())
 
135
    return [self.get_passwords(acct) for acct in data]
 
136
 
 
137
  def get_account(self, id):
 
138
    data = json.loads(self.dispatcher.accounts.Get(id))
 
139
    return self.get_passwords(data)
 
140
 
 
141
  def handle_max_id(self, acct, opname, id=None):
 
142
    if not id: id = acct["id"]
 
143
 
 
144
    features = SERVICES[acct["service"]]["features"]
 
145
 
 
146
    if "sincetime" in features: select = "time"
 
147
    elif "sinceid" in features: select = "cast(mid as integer)"
 
148
    else: return {}
 
149
    
 
150
    query = """
 
151
            SELECT max(%s) FROM messages
 
152
            WHERE (account = '%s' or transient = '%s') AND operation = '%s'
 
153
            """ % (select, id, id, opname)
 
154
 
 
155
    with self.dispatcher.messages.db:
 
156
      result = self.dispatcher.messages.db.execute(query).fetchall()[0][0]
 
157
      if result: return {"since": result}
 
158
 
 
159
    return {}
 
160
 
 
161
  def validate_operation(self, acct, opname, enabled="receive_enabled"):
 
162
    # if account doesn't have the required feature or is disabled, return
 
163
    if enabled in acct:
 
164
      if not acct[enabled]: return
 
165
    else: 
 
166
      return
 
167
    # if there is an account for a service that gwibber doesn't no about, return
 
168
    if not acct["service"] in SERVICES: return
 
169
    service = SERVICES[acct["service"]]
 
170
    return acct["service"] in PROTOCOLS and \
 
171
           opname in service["features"] and \
 
172
           opname in FEATURES and acct[enabled]
 
173
 
 
174
  def stream_to_operation(self, stream):
 
175
    try:
 
176
      account = self.get_account(stream["account"])
 
177
    except:
 
178
      self.dispatcher.streams.Delete(stream["id"])
 
179
      return None
 
180
    args = stream["parameters"]
 
181
    opname = stream["operation"]
 
182
    if self.validate_operation(account, opname):
 
183
      args.update(self.handle_max_id(account, opname, stream["id"]))
 
184
      return (account, stream["operation"], args, stream["id"])
 
185
 
 
186
  def search_to_operations(self, search):
 
187
    for account in self.get_accounts():
 
188
      args = {"query": search["query"]}
 
189
      if self.validate_operation(account, "search"):
 
190
        args.update(self.handle_max_id(account, "search", search["id"]))
 
191
        yield (account, "search", args, search["id"])
 
192
 
 
193
  def account_to_operations(self, acct):
 
194
    if isinstance(acct, basestring):
 
195
      acct = self.get_account(acct)
 
196
    
 
197
    if SERVICES.has_key(acct["service"]):
 
198
      for opname in SERVICES[acct["service"]]["default_streams"]:
 
199
        if self.validate_operation(acct, opname):
 
200
          args = self.handle_max_id(acct, opname)
 
201
          yield (acct, opname, args, False)
 
202
 
 
203
  def get_send_operations(self, message):
 
204
    for account in self.get_accounts():
 
205
      if self.validate_operation(account, "send", "send_enabled"):
 
206
        yield (account, "send", {"message": message}, False)
 
207
 
 
208
  def get_operation_by_id(self, id):
 
209
    stream = self.dispatcher.streams.Get(id)
 
210
    if stream: return [self.stream_to_operation(json.loads(stream))]
 
211
    
 
212
    search = self.dispatcher.searches.Get(id)
 
213
    if search: return list(self.search_to_operations(json.loads(search)))
 
214
 
 
215
  def get_operations(self):
 
216
    for acct in self.get_accounts():
 
217
      for o in self.account_to_operations(acct):
 
218
        yield o
 
219
 
 
220
    for stream in json.loads(self.dispatcher.streams.List()):
 
221
      # TODO: Make sure account for stream exists
 
222
      o = self.stream_to_operation(stream)
 
223
      if o: yield o
 
224
 
 
225
    for search in json.loads(self.dispatcher.searches.List()):
 
226
      for o in self.search_to_operations(search):
 
227
        yield o
 
228
 
 
229
class MapAsync(threading.Thread):
 
230
  def __init__(self, func, iterable, cbsuccess, cbfailure, pool):
 
231
    threading.Thread.__init__(self)
 
232
    self.iterable = iterable
 
233
    self.callback = cbsuccess
 
234
    self.failure = cbfailure
 
235
    self.daemon = True
 
236
    self.func = func
 
237
    self.pool = pool
 
238
    self.start()
 
239
 
 
240
  def run(self):
 
241
    try:
 
242
      self.pool.map_async(self.func, self.iterable, callback = self.callback)
 
243
    except Exception as e:
 
244
      self.failure(e, traceback.format_exc())
 
245
 
 
246
class Dispatcher(dbus.service.Object):
 
247
  """
 
248
  The Gwibber Dispatcher handles all the backend operations.
 
249
  """
 
250
  __dbus_object_path__ = "/com/gwibber/Service"
 
251
 
 
252
  def __init__(self, loop, autorefresh=True):
 
253
    self.bus = dbus.SessionBus()
 
254
    bus_name = dbus.service.BusName("com.Gwibber.Service", bus=self.bus)
 
255
    dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
 
256
 
 
257
    self.db = sqlite3.connect(SQLITE_DB_FILENAME)
 
258
 
 
259
    self.accounts = storage.AccountManager(self.db)
 
260
    self.searches = storage.SearchManager(self.db)
 
261
    self.streams = storage.StreamManager(self.db)
 
262
    self.messages = storage.MessageManager(self.db)
 
263
    self.collector = OperationCollector(self)
 
264
 
 
265
    # Monitor the connection
 
266
    self.connection_monitor = util.getbus("Connection")
 
267
    self.connection_monitor.connect_to_signal("ConnectionOnline", self.on_connection_online)
 
268
    self.connection_monitor.connect_to_signal("ConnectionOffline", self.on_connection_offline)
 
269
 
 
270
    self.indicate = None 
 
271
 
 
272
    if indicate and util.resources.get_desktop_file():
 
273
      self.indicate = indicate.indicate_server_ref_default()
 
274
      self.indicate.set_type("message.gwibber")
 
275
      self.indicate.set_desktop_file(util.resources.get_desktop_file())
 
276
      self.indicate.connect("server-display", self.on_indicator_server_activate)
 
277
      self.indicate.connect("interest-added", self.on_indicator_interest_added)
 
278
      self.indicate.connect("interest-removed", self.on_indicator_interest_removed)
 
279
      self.indicate.show()
 
280
    self.indicator_items = {}
 
281
    self.notified_items = []
 
282
    self.notified_errors = {}
 
283
    self.messages_indicator = None
 
284
    self.replies_indicator = None
 
285
    self.private_indicator = None
 
286
    self.unseen_counts = {}
 
287
    for s in "messages", "replies", "private":
 
288
      self.unseen_counts[s] = 0
 
289
 
 
290
    if Unity and Dbusmenu:
 
291
      launcher = Unity.LauncherEntry.get_for_desktop_id ("gwibber.desktop")
 
292
      ql = Dbusmenu.Menuitem.new ()
 
293
      refresh_menu = Dbusmenu.Menuitem.new ()
 
294
      refresh_menu.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Refresh"))
 
295
      refresh_menu.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
 
296
      refresh_menu.connect("item-activated", self.refresh)
 
297
      ql.child_append (refresh_menu)
 
298
      accounts_menu = Dbusmenu.Menuitem.new ()
 
299
      accounts_menu.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Accounts"))
 
300
      accounts_menu.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
 
301
      accounts_menu.connect("item-activated", self.show_accounts)
 
302
      ql.child_append (accounts_menu)
 
303
      preferences_menu = Dbusmenu.Menuitem.new ()
 
304
      preferences_menu.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Preferences"))
 
305
      preferences_menu.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
 
306
      preferences_menu.connect("item-activated", self.show_preferences)
 
307
      ql.child_append (preferences_menu)
 
308
      quit_menu = Dbusmenu.Menuitem.new ()
 
309
      quit_menu.property_set (Dbusmenu.MENUITEM_PROP_LABEL, _("Quit"))
 
310
      quit_menu.property_set_bool (Dbusmenu.MENUITEM_PROP_VISIBLE, True)
 
311
      quit_menu.connect("item-activated", self.shutdown)
 
312
      ql.child_append (quit_menu)
 
313
      launcher.set_property("quicklist", ql)
 
314
 
 
315
    self.refresh_count = 0
 
316
    self.mainloop = loop
 
317
    self.workerpool = multiprocessing.Pool()
 
318
 
 
319
    self.refresh_timer_id = None
 
320
 
 
321
    self.maintDone = False
 
322
    self.maintRunning = False
 
323
    self.refreshRunning = False
 
324
 
 
325
    if autorefresh:
 
326
      if self.refresh_timer_id:
 
327
        gobject.source_remove(self.refresh_timer_id)
 
328
      # wait a few seconds before alerting the world we are online
 
329
      self.refresh_timer_id = gobject.timeout_add_seconds(int(10), self.refresh)
 
330
 
 
331
    self.accounts_service = util.getbus("Accounts")
 
332
    self.accounts_service.connect_to_signal("Updated", self.on_account_updated)
 
333
    self.accounts_service.connect_to_signal("Deleted", self.on_account_deleted)
 
334
    self.accounts_service.connect_to_signal("Created", self.on_account_created)
 
335
 
 
336
  def show_preferences(self, *args):
 
337
    subprocess.Popen("gwibber-preferences", shell=False)
 
338
 
 
339
  def show_accounts(self, *args):
 
340
    subprocess.Popen("gwibber-accounts", shell=False)
 
341
 
 
342
  def shutdown(self, *args):
 
343
    subprocess.Popen(["pkill", "gwibber"], shell=False)
 
344
    self.Quit()
 
345
 
 
346
  def do_maintenance(self, *args):
 
347
    # perform some needed MessageManager maintenance
 
348
    if self.maint_timer_id:
 
349
      gobject.source_remove(self.maint_timer_id)
 
350
    if self.refreshRunning:
 
351
      self.maint_timer_id = gobject.timeout_add_seconds(60, self.do_maintenance)
 
352
      return False
 
353
 
 
354
    self.maintRunning = True
 
355
    self.messages.maintenance()
 
356
    self.maintRunning = False
 
357
    self.maintDone = True
 
358
    return False
 
359
    
 
360
  def on_connection_online(self, *args):
 
361
    log.logger.info("Dispatcher Online, initiating a refresh")
 
362
    if self.refresh_timer_id:
 
363
      gobject.source_remove(self.refresh_timer_id)
 
364
    # wait a few seconds before alerting the world we are online
 
365
    self.refresh_timer_id = gobject.timeout_add_seconds(int(10), self.refresh)
 
366
 
 
367
  def on_connection_offline(self, *args):
 
368
    self.refreshRunning = False
 
369
    log.logger.info("Dispatcher Offline, suspending operations")
 
370
    if self.refresh_timer_id:
 
371
      gobject.source_remove(self.refresh_timer_id)
 
372
 
 
373
  def on_account_updated(self, account):
 
374
    pass
 
375
 
 
376
  def on_account_created(self, account):
 
377
    self.refresh()
 
378
 
 
379
  def on_account_deleted(self, account):
 
380
    # Delete streams associated with the user that was deleted
 
381
    try:
 
382
      acct = json.loads(account)
 
383
      for stream in json.loads(self.streams.List()):
 
384
        if stream["account"] == acct["id"]:
 
385
          self.streams.Delete(stream["id"])
 
386
    except:
 
387
      pass
 
388
 
 
389
  @dbus.service.signal("com.Gwibber.Service")
 
390
  def LoadingComplete(self):
 
391
    self.refreshRunning = False
 
392
 
 
393
  @dbus.service.signal("com.Gwibber.Service")
 
394
  def LoadingStarted(self):
 
395
    self.refreshRunning = True
 
396
 
 
397
  @dbus.service.method("com.Gwibber.Service")
 
398
  def Refresh(self):
 
399
    """
 
400
    Calls the Gwibber Service to trigger a refresh operation
 
401
    example:
 
402
            import dbus
 
403
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
404
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
405
            service.Refresh()
 
406
 
 
407
    """
 
408
    self.refresh()
 
409
 
 
410
  @dbus.service.method("com.Gwibber.Service", in_signature="s")
 
411
  def PerformOp(self, opdata):
 
412
    try: o = json.loads(opdata)
 
413
    except: return
 
414
    
 
415
    log.logger.debug("** Starting Single Operation **")
 
416
    self.LoadingStarted()
 
417
    
 
418
    params = ["account", "operation", "args", "transient"]
 
419
    operation = None
 
420
    
 
421
    if "account" in o and self.collector.get_account(o["account"]):
 
422
      account = self.collector.get_account(o["account"])
 
423
    
 
424
    if "id" in o:
 
425
      operation = self.collector.get_operation_by_id(o["id"])
 
426
    elif "operation" in o and self.collector.validate_operation(account, o["operation"]):
 
427
        operation = util.compact([(account, o["operation"], o["args"], None)])
 
428
 
 
429
    if operation:
 
430
      self.perform_async_operation(operation)
 
431
 
 
432
  @dbus.service.method("com.Gwibber.Service", in_signature="s")
 
433
  def UpdateIndicators(self, stream):
 
434
    """
 
435
    Update counts in messaging indicators
 
436
    example:
 
437
            import dbus
 
438
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
439
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
440
            service.UpdateIndicators("stream")
 
441
    """
 
442
    self.handle_indicator_counts(stream)
 
443
 
 
444
  @dbus.service.method("com.Gwibber.Service", in_signature="s")
 
445
  def SendMessage(self, message):
 
446
    """
 
447
    Posts a message/status update to all accounts with send_enabled = True.  It 
 
448
    takes one argument, which is a message formated as a string.
 
449
    example:
 
450
            import dbus
 
451
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
452
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
453
            service.SendMessage("Your message")
 
454
    """
 
455
    self.send(list(self.collector.get_send_operations(message)))
 
456
 
 
457
  @dbus.service.method("com.Gwibber.Service", in_signature="s")
 
458
  def Send(self, opdata):
 
459
    try:
 
460
      o = json.loads(opdata)
 
461
      if "target" in o:
 
462
        args = {"message": o["message"], "target": o["target"]}
 
463
        operations = [(self.collector.get_account(o["target"]["account"]), "send_thread", args, None)]
 
464
      elif "private" in o:
 
465
        args = {"message": o["message"], "private": o["private"]}
 
466
        operations = [(self.collector.get_account(o["private"]["account"]), "send_private", args, None)]
 
467
      elif "accounts" in o:
 
468
        operations = [(self.collector.get_account(a), "send", {"message": o["message"]}, None) for a in o["accounts"]]
 
469
      self.send(operations)
 
470
    except:
 
471
      log.logger.error("Sending failed:\n%s", traceback.format_exc())
 
472
 
 
473
  @dbus.service.method("com.Gwibber.Service", out_signature="s")
 
474
  def GetServices(self):
 
475
    """
 
476
    Returns a list of services available as json string
 
477
    example:
 
478
            import dbus, json
 
479
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
480
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
481
            services = json.loads(service.GetServices())
 
482
 
 
483
    """
 
484
    return json.dumps(SERVICES)
 
485
 
 
486
  @dbus.service.method("com.Gwibber.Service", out_signature="s")
 
487
  def GetFeatures(self):
 
488
    """
 
489
    Returns a list of features as json string
 
490
    example:
 
491
            import dbus, json
 
492
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
493
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
494
            features = json.loads(service.GetFeatures())
 
495
    """
 
496
    return json.dumps(FEATURES)
 
497
 
 
498
  @dbus.service.method("com.Gwibber.Service", out_signature="s")
 
499
  def GetVersion(self): 
 
500
    """
 
501
    Returns a the gwibber-service version as a string
 
502
    example:
 
503
            import dbus
 
504
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
505
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
506
            version = service.GetVersion()
 
507
    """
 
508
    return VERSION_NUMBER
 
509
 
 
510
  @dbus.service.method("com.Gwibber.Service")
 
511
  def Start(self):
 
512
    """
 
513
    Start the service
 
514
    example:
 
515
            import dbus
 
516
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
517
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
518
            service.Start()
 
519
    """
 
520
    log.logger.info("Gwibber Service is starting")
 
521
 
 
522
  @dbus.service.method("com.Gwibber.Service")
 
523
  def Quit(self): 
 
524
    """
 
525
    Shutdown the service
 
526
    example:
 
527
            import dbus
 
528
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
529
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
530
            service.Quit()
 
531
    """
 
532
    log.logger.info("Gwibber Service is being shutdown")
 
533
    self.mainloop.quit()
 
534
 
 
535
  @dbus.service.method("com.Gwibber.Service", out_signature="b")
 
536
  def IndicatorInterestCheck(self):
 
537
    """
 
538
    Check for interest from the messaging menu indicator
 
539
    Returns a boolean
 
540
    example:
 
541
            import dbus
 
542
            obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
 
543
            service = dbus.Interface(obj, "com.Gwibber.Service")
 
544
            res = service.IndicatorInterestCheck()
 
545
    """
 
546
    if indicate and self.indicate:
 
547
      return self.indicate.check_interest(indicate.INTEREST_SERVER_DISPLAY)
 
548
    else:
 
549
      return False
 
550
 
 
551
  @dbus.service.signal("com.Gwibber.Service")
 
552
  def IndicatorInterestAdded(self): pass
 
553
  
 
554
  @dbus.service.signal("com.Gwibber.Service")
 
555
  def IndicatorInterestRemoved(self): pass
 
556
 
 
557
  @dbus.service.signal("com.Gwibber.Service", signature="s")
 
558
  def Error(self, error): pass
 
559
 
 
560
  @dbus.service.method("com.Gwibber.Service", in_signature="s")
 
561
  def clear_error(self, error):
 
562
    error = json.loads(error)
 
563
    self.notified_errors["account"]["service"] = None
 
564
    
 
565
  def send_error_notify(self, error):
 
566
    error = json.loads(error)["error"]
 
567
 
 
568
    if self.notified_errors.has_key(error["account"]["service"]):
 
569
      if self.notified_errors[error["account"]["service"]] == error["message"]:
 
570
        return
 
571
    if util.can_notify:
 
572
      icon = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % error["account"]["service"])
 
573
      if not icon:
 
574
        icon = util.resources.get_ui_asset("gwibber.svg")
 
575
      util.notify(error["account"]["service"], error["message"], icon, 2000)
 
576
    self.notified_errors[error["account"]["service"]] = error["message"]
 
577
 
 
578
  def perform_async_operation(self, iterable):
 
579
    t = MapAsync(perform_operation, iterable, self.loading_complete, self.loading_failed, self.workerpool)
 
580
    t.join()
 
581
  
 
582
  def loading_complete(self, output):
 
583
    self.refresh_count += 1
 
584
    
 
585
    items = []
 
586
    errors = []
 
587
    for o in output:
 
588
      for o2 in o[1]:
 
589
        if len(o2) > 1:
 
590
          if o2[0] != "error":
 
591
            with sqlite3.connect(SQLITE_DB_FILENAME) as db:
 
592
              if len(db.execute("""select * from messages where mid = '%s' and account = '%s' and stream = '%s'""" % (o2[1], o2[2], o2[6])).fetchall()) > 0:
 
593
                self.messages.Message("update", o2[-1])
 
594
              else:
 
595
                self.messages.Message("new", o2[-1])
 
596
            items.append(o2)
 
597
          else:
 
598
            errors.append(o2)
 
599
    with sqlite3.connect(SQLITE_DB_FILENAME) as db:
 
600
      oldid = db.execute("select max(ROWID) from messages").fetchone()[0] or 0
 
601
      
 
602
      output = db.executemany("INSERT OR REPLACE INTO messages (%s) VALUES (%s)" % (
 
603
            ",".join(self.messages.columns),
 
604
            ",".join("?" * len(self.messages.columns))), items)
 
605
 
 
606
      for s in "messages", "replies", "private":
 
607
        if s == "messages": 
 
608
          to_me = 0 
 
609
        else: to_me = 1
 
610
        count = db.execute("SELECT count(mid) FROM messages WHERE stream = ? AND from_me = 0 and to_me = ? AND ROWID > ?", (s,to_me,oldid)).fetchone()[0] or 0
 
611
        self.unseen_counts[s] = self.unseen_counts[s] + int(count)
 
612
  
 
613
      self.update_indicators(self.unseen_counts)
 
614
 
 
615
      new_items = db.execute("""
 
616
        select * from (select * from messages where operation == "receive" and ROWID > %s and to_me = 0 ORDER BY time DESC LIMIT 10) as a union
 
617
        select * from (select * from messages where operation IN ("receive","private") and ROWID > %s and to_me != 0 ORDER BY time DESC LIMIT 10) as b
 
618
        ORDER BY time ASC""" % (oldid, oldid)).fetchall()
 
619
 
 
620
      for i in new_items:
 
621
          self.new_message(i)
 
622
    
 
623
    for error in errors:
 
624
      self.Error(error[1])
 
625
      self.send_error_notify(error[1])
 
626
 
 
627
    self.LoadingComplete()
 
628
    log.logger.info("Loading complete: %s - %s", self.refresh_count, [o[0] for o in output])
 
629
 
 
630
  def update_indicators(self, counts):
 
631
    if indicate:
 
632
      if counts.has_key("private"):
 
633
        if not self.private_indicator:
 
634
          self.private_indicator = indicate.Indicator() if hasattr(indicate, "Indicator") else indicate.IndicatorMessage()
 
635
          self.private_indicator.connect("user-display", self.on_indicator_activate)
 
636
          self.private_indicator.set_property("name", _("Private"))
 
637
          self.private_indicator.set_property("stream", "private")
 
638
          self.private_indicator.show()
 
639
        self.private_indicator.set_property("count", str(counts["private"]))
 
640
        if counts["private"] > 0:
 
641
          self.private_indicator.set_property_bool("draw-attention", True)
 
642
        if self.private_indicator not in self.indicator_items:
 
643
          self.indicator_items["private"] = self.private_indicator
 
644
        log.logger.debug("Private Messages Indicator count updated to %s", counts["private"])
 
645
      if counts.has_key("replies"):
 
646
        if not self.replies_indicator:
 
647
          self.replies_indicator = indicate.Indicator() if hasattr(indicate, "Indicator") else indicate.IndicatorMessage()
 
648
          self.replies_indicator.connect("user-display", self.on_indicator_activate)
 
649
          self.replies_indicator.set_property("name", _("Replies"))
 
650
          self.replies_indicator.set_property("stream", "replies")
 
651
          self.replies_indicator.show()
 
652
        self.replies_indicator.set_property("count", str(counts["replies"]))
 
653
        if self.replies_indicator not in self.indicator_items:
 
654
          self.indicator_items["replies"] = self.replies_indicator
 
655
        log.logger.debug("Replies Indicator count updated to %s", counts["replies"])
 
656
      if counts.has_key("messages"):
 
657
        if not self.messages_indicator:
 
658
          self.messages_indicator = indicate.Indicator() if hasattr(indicate, "Indicator") else indicate.IndicatorMessage()
 
659
          self.messages_indicator.connect("user-display", self.on_indicator_activate)
 
660
          self.messages_indicator.set_property("name", _("Messages"))
 
661
          self.messages_indicator.set_property("stream", "messages")
 
662
          self.messages_indicator.show()
 
663
        self.messages_indicator.set_property("count", str(counts["messages"]))
 
664
        if self.messages_indicator not in self.indicator_items:
 
665
          self.indicator_items["messages"] = self.messages_indicator
 
666
        log.logger.debug("Messages Indicator count updated to %s", counts["messages"])
 
667
 
 
668
  def on_indicator_interest_added(self, server, interest):
 
669
    self.IndicatorInterestAdded()
 
670
 
 
671
  def on_indicator_interest_removed(self, server, interest):
 
672
    self.IndicatorInterestRemoved()
 
673
 
 
674
  def on_indicator_server_activate(self, indicator, timestamp=None):
 
675
    dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
676
    client_bus = dbus.SessionBus()
 
677
    log.logger.debug("Raising gwibber client")
 
678
    try:
 
679
      client_obj = client_bus.get_object("com.GwibberClient",
 
680
        "/com/GwibberClient", follow_name_owner_changes = True,
 
681
        introspect = False)
 
682
      gw = dbus.Interface(client_obj, "com.GwibberClient")
 
683
      gw.focus_client(reply_handler=self.handle_focus_reply,
 
684
                      error_handler=self.handle_focus_error)
 
685
    except dbus.DBusException:
 
686
      log.logger.error("Indicator activate failed:\n%s", traceback.format_exc())
 
687
 
 
688
  def on_indicator_activate(self, indicator, timestamp=None):
 
689
    if not indicate: return
 
690
    stream = indicator.get_property("stream")
 
691
    log.logger.debug("Raising gwibber client, focusing %s stream", stream)
 
692
    try:
 
693
      self.handle_indicator_counts(stream)
 
694
    except:
 
695
      pass
 
696
    client_bus = dbus.SessionBus()
 
697
    try:
 
698
      client_obj = client_bus.get_object("com.GwibberClient", "/com/GwibberClient")
 
699
      gw = dbus.Interface(client_obj, "com.GwibberClient")
 
700
      gw.show_stream(stream, reply_handler=self.handle_focus_reply,
 
701
                      error_handler=self.handle_focus_error)
 
702
    except dbus.DBusException:
 
703
      log.logger.error("Indicator activation failed:\n%s", traceback.format_exc())
 
704
 
 
705
  def handle_focus_reply(self, *args):
 
706
    log.logger.debug("Gwibber Client raised")
 
707
 
 
708
  def handle_focus_error(self, *args):
 
709
    log.logger.error("Failed to raise client %s", args)
 
710
 
 
711
  def handle_indicator_counts(self, stream):
 
712
    if not indicate: return
 
713
    if self.indicator_items.has_key(stream):
 
714
      self.indicator_items[stream].set_property("count", str(0))
 
715
      if stream == "private":
 
716
        self.private_indicator.set_property_bool("draw-attention", False)
 
717
    self.unseen_counts[stream] = 0
 
718
 
 
719
  def new_message(self, data):
 
720
    message = json.loads(data[-1])
 
721
    if message["transient"]:
 
722
      log.logger.debug("Message %s is transient, not notifying", message["id"])
 
723
      return 
 
724
 
 
725
    if util.can_notify and  str(message["mid"]) not in self.notified_items:
 
726
      self.notified_items.append(message["mid"])
 
727
      if SETTINGS["notify_mentions_only"] and message["to_me"]: 
 
728
        log.logger.debug("%s is a mention and notify_mentions_only is true", message["mid"])
 
729
        gobject.idle_add(self.handle_notify_item, message)
 
730
      elif SETTINGS["show_notifications"] and not SETTINGS["notify_mentions_only"]:
 
731
        log.logger.debug("%s - show_notifications is true and notify_mentions_only is false", message["mid"])
 
732
        gobject.idle_add(self.handle_notify_item, message)
 
733
 
 
734
  def handle_notify_item(self, message):
 
735
    if SETTINGS["show_fullname"]:
 
736
      sender_name = message["sender"].get("name", message["sender"].get("nick", ""))
 
737
    else:
 
738
      sender_name = message["sender"].get("nick", message["sender"].get("name", ""))
 
739
 
 
740
    notify_text = ""
 
741
    if len(message["text"]) > 0: 
 
742
      notify_text = message["text"]
 
743
    elif message.has_key("stream"):
 
744
      if message["stream"] == "images":
 
745
        notify_text = _("has shared a photo")
 
746
      if message["stream"] == "links":
 
747
        notify_text = _("has shared a link")
 
748
      if message["stream"] == "videos":
 
749
        notify_text = _("has shared a video")
 
750
    #image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"])
 
751
    if message["sender"].has_key("image"):
 
752
      image = util.resources.get_avatar_path(message["sender"]["image"])
 
753
    else:
 
754
      image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"])
 
755
    if not image:
 
756
      image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"])
 
757
    util.notify(sender_name, notify_text, image, 2000)
 
758
 
 
759
    return False
 
760
 
 
761
  def loading_failed(self, exception, tb):
 
762
    self.LoadingComplete()
 
763
    log.logger.error("Loading failed: %s - %s", exception, tb)
 
764
 
 
765
  def send(self, operations):
 
766
    operations = util.compact(operations)
 
767
    if operations:
 
768
      self.LoadingStarted()
 
769
      log.logger.debug("*** Sending Message ***")
 
770
      self.perform_async_operation(operations)
 
771
 
 
772
  def refresh(self, *args):
 
773
    if self.refresh_timer_id:
 
774
      gobject.source_remove(self.refresh_timer_id)
 
775
 
 
776
    if not self.maintRunning and not self.refreshRunning:
 
777
      log.logger.debug("Refresh interval is set to %s", SETTINGS["interval"])
 
778
      operations = []
 
779
    
 
780
      for o in self.collector.get_operations():
 
781
        interval = FEATURES[o[1]].get("interval", 1)
 
782
        if self.refresh_count % interval == 0:
 
783
          operations.append(o)
 
784
    
 
785
      if operations:
 
786
        log.logger.debug("** Starting Refresh - %s **", mx.DateTime.now())
 
787
        self.LoadingStarted()
 
788
        self.perform_async_operation(operations)
 
789
 
 
790
      self.refresh_timer_id = gobject.timeout_add_seconds(int(60 * SETTINGS["interval"]), self.refresh)
 
791
    else:
 
792
      self.refresh_timer_id = gobject.timeout_add_seconds(int(30), self.refresh)
 
793
 
 
794
    if not self.maintDone:
 
795
      self.maint_timer_id = gobject.timeout_add_seconds(60, self.do_maintenance)
 
796
 
 
797
    return False
 
798
 
 
799
class ConnectionMonitor(dbus.service.Object):
 
800
  __dbus_object_path__ = "/com/gwibber/Connection"
 
801
 
 
802
  def __init__(self):
 
803
    self.bus = dbus.SessionBus()
 
804
    bus_name = dbus.service.BusName("com.Gwibber.Connection", bus=self.bus)
 
805
    dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
 
806
 
 
807
    self.sysbus = dbus.SystemBus()
 
808
 
 
809
    self.has_nm = None
 
810
 
 
811
    try:
 
812
      self.nm = self.sysbus.get_object(NM_DBUS_SERVICE, NM_DBUS_OBJECT_PATH)
 
813
      self.nm.connect_to_signal("StateChanged", self.on_connection_changed)
 
814
      self.has_nm = True
 
815
    except:
 
816
      pass
 
817
 
 
818
  def on_connection_changed(self, state):
 
819
    log.logger.debug("Network state changed, new state is %d", state)
 
820
    """
 
821
    NM_STATE_UNKNOWN = 0
 
822
    NM_STATE_ASLEEP = 1
 
823
    NM_STATE_CONNECTING = 2
 
824
    NM_STATE_CONNECTED = 3
 
825
    NM_STATE_DISCONNECTED = 4
 
826
    """
 
827
 
 
828
    if state == NM_STATE_CONNECTED:
 
829
      log.logger.info("Network state changed to Online")
 
830
      self.ConnectionOnline()
 
831
    else:
 
832
      log.logger.info("Network state changed to Offline")
 
833
      self.ConnectionOffline()
 
834
 
 
835
  @dbus.service.signal("com.Gwibber.Connection")
 
836
  def ConnectionOnline(self): pass
 
837
 
 
838
  @dbus.service.signal("com.Gwibber.Connection")
 
839
  def ConnectionOffline(self): pass
 
840
 
 
841
  @dbus.service.method("com.Gwibber.Connection")
 
842
  def isConnected(self):
 
843
    if not self.has_nm: 
 
844
      log.logger.info("Can't determine network state, assuming online")
 
845
      return True
 
846
    try:
 
847
      if self.nm.state() == NM_STATE_CONNECTED:
 
848
        return True
 
849
      else:
 
850
        return False
 
851
    except:
 
852
      return True
 
853
 
 
854
class URLShorten(dbus.service.Object):
 
855
  __dbus_object_path__ = "/com/gwibber/URLShorten"
 
856
 
 
857
  def __init__(self):
 
858
    self.bus = dbus.SessionBus()
 
859
    bus_name = dbus.service.BusName("com.Gwibber.URLShorten", bus=self.bus)
 
860
    dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
 
861
 
 
862
  @dbus.service.method("com.Gwibber.URLShorten", in_signature="s", out_signature="s")
 
863
  def Shorten(self, url):
 
864
    """
 
865
    Takes a url as a string and returns a shortened url as a string.
 
866
    example:
 
867
            import dbus
 
868
            url = "http://www.example.com/this/is/a/long/url"
 
869
            obj = dbus.SessionBus().get_object("com.Gwibber.URLShorten", "/com/gwibber/URLShorten")
 
870
            shortener = dbus.Interface(obj, "com.Gwibber.URLShorten")
 
871
            short_url = shortener.Shorten(url)
 
872
    """
 
873
    
 
874
    service = SETTINGS["urlshorter"] or "is.gd"
 
875
    log.logger.info("Shortening URL %s with %s", url, service)
 
876
    if self.IsShort(url): return url
 
877
    try:
 
878
      s = urlshorter.PROTOCOLS[service].URLShorter()
 
879
      return s.short(url)
 
880
    except: return url
 
881
 
 
882
  def IsShort(self, url):
 
883
    for us in urlshorter.PROTOCOLS.values():
 
884
      if url.startswith(us.PROTOCOL_INFO["fqdn"]):
 
885
        return True
 
886
    return False
 
887
 
 
888
class Translate(dbus.service.Object):
 
889
  __dbus_object_path__ = "/com/gwibber/Translate"
 
890
 
 
891
  def __init__(self):
 
892
    self.bus = dbus.SessionBus()
 
893
    bus_name = dbus.service.BusName("com.Gwibber.Translate", bus=self.bus)
 
894
    dbus.service.Object.__init__(self, bus_name, self.__dbus_object_path__)
 
895
 
 
896
  @dbus.service.method("com.Gwibber.Translate", in_signature="sss", out_signature="s")
 
897
  def Translate(self, text, srclang, dstlang):
 
898
    url = "http://ajax.googleapis.com/ajax/services/language/translate"
 
899
    params = {"v": "1.0", "q": text, "langpair": "|".join((srclang, dstlang))}
 
900
    data = network.Download(url, params).get_json()
 
901
 
 
902
    if data["responseStatus"] == 200:
 
903
      return data.get("responseData", {}).get("translatedText", "")
 
904
    else: return ""
 
905