2
# -*- coding: utf-8 -*-
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 _
12
from util import resources
13
from util import exceptions
14
from util.const import *
18
from gi.repository import Unity, Dbusmenu
23
# Try to import * from custom, install custom.py to include packaging
24
# customizations like distro API keys, etc
26
from util.custom import *
35
gobject.threads_init()
37
log.logger.name = "Gwibber Dispatcher"
39
# Dynamically build a list of available service plugins
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)
48
FEATURES = json.loads(GWIBBER_OPERATIONS)
49
SERVICES = dict([(k, v.PROTOCOL_INFO) for k, v in PROTOCOLS.items()])
50
SETTINGS = config.Preferences()
52
if SETTINGS["interval"] < 5:
53
#log.logger.info( "Migrating refresh interval from %d to %d", SETTINGS["interval"], 5)
54
SETTINGS["interval"] = 5
57
def perform_operation((account, opname, args, transient)):
59
stream = FEATURES[opname]["stream"] or opname
60
logtext = "<%s:%s>" % (account["service"], opname)
62
logtext = "<%s:%s>" % (account["service"], opname)
63
log.logger.debug("%s Performing operation", logtext)
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
70
if message_data is not None:
71
for m in message_data:
73
if isinstance(m, dict) and m.has_key("mid"):
74
m["id"] = uuid.uuid1().hex
75
m["operation"] = opname
77
m["transient"] = transient
78
m["rtl"] = util.isRTL(re.sub(text_cleaner, "", m["text"].decode('utf-8')))
80
if m["type"] == "link": m["stream"] = "links"
81
if m["type"] == "video": m["stream"] = "videos"
82
if m["type"] == "photo": m["stream"] = "images"
84
log.logger.debug("%s Adding record", logtext)
86
new_messages.insert(0, (
93
m["stream"] or stream,
96
m.get("sender", {}).get("is_me", None),
98
m.get("sender", {}).get("nick", None),
99
m.get("reply", {}).get("nick", None),
102
elif isinstance(m, dict) and m.has_key("error"):
103
new_messages.insert(0, (
107
except Exception as e:
108
if not "logtext" in locals(): logtext = "<UNKNOWN>"
109
log.logger.error("%s Operation failed", logtext)
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())
119
class OperationCollector:
120
def __init__(self, dispatcher):
121
self.dispatcher = dispatcher
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)
128
acct[key] = self.dispatcher.accounts.passwords[id]
133
def get_accounts(self):
134
data = json.loads(self.dispatcher.accounts.List())
135
return [self.get_passwords(acct) for acct in data]
137
def get_account(self, id):
138
data = json.loads(self.dispatcher.accounts.Get(id))
139
return self.get_passwords(data)
141
def handle_max_id(self, acct, opname, id=None):
142
if not id: id = acct["id"]
144
features = SERVICES[acct["service"]]["features"]
146
if "sincetime" in features: select = "time"
147
elif "sinceid" in features: select = "cast(mid as integer)"
151
SELECT max(%s) FROM messages
152
WHERE (account = '%s' or transient = '%s') AND operation = '%s'
153
""" % (select, id, id, opname)
155
with self.dispatcher.messages.db:
156
result = self.dispatcher.messages.db.execute(query).fetchall()[0][0]
157
if result: return {"since": result}
161
def validate_operation(self, acct, opname, enabled="receive_enabled"):
162
# if account doesn't have the required feature or is disabled, return
164
if not acct[enabled]: 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]
174
def stream_to_operation(self, stream):
176
account = self.get_account(stream["account"])
178
self.dispatcher.streams.Delete(stream["id"])
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"])
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"])
193
def account_to_operations(self, acct):
194
if isinstance(acct, basestring):
195
acct = self.get_account(acct)
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)
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)
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))]
212
search = self.dispatcher.searches.Get(id)
213
if search: return list(self.search_to_operations(json.loads(search)))
215
def get_operations(self):
216
for acct in self.get_accounts():
217
for o in self.account_to_operations(acct):
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)
225
for search in json.loads(self.dispatcher.searches.List()):
226
for o in self.search_to_operations(search):
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
242
self.pool.map_async(self.func, self.iterable, callback = self.callback)
243
except Exception as e:
244
self.failure(e, traceback.format_exc())
246
class Dispatcher(dbus.service.Object):
248
The Gwibber Dispatcher handles all the backend operations.
250
__dbus_object_path__ = "/com/gwibber/Service"
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__)
257
self.db = sqlite3.connect(SQLITE_DB_FILENAME)
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)
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)
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)
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
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)
315
self.refresh_count = 0
317
self.workerpool = multiprocessing.Pool()
319
self.refresh_timer_id = None
321
self.maintDone = False
322
self.maintRunning = False
323
self.refreshRunning = False
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)
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)
336
def show_preferences(self, *args):
337
subprocess.Popen("gwibber-preferences", shell=False)
339
def show_accounts(self, *args):
340
subprocess.Popen("gwibber-accounts", shell=False)
342
def shutdown(self, *args):
343
subprocess.Popen(["pkill", "gwibber"], shell=False)
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)
354
self.maintRunning = True
355
self.messages.maintenance()
356
self.maintRunning = False
357
self.maintDone = True
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)
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)
373
def on_account_updated(self, account):
376
def on_account_created(self, account):
379
def on_account_deleted(self, account):
380
# Delete streams associated with the user that was deleted
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"])
389
@dbus.service.signal("com.Gwibber.Service")
390
def LoadingComplete(self):
391
self.refreshRunning = False
393
@dbus.service.signal("com.Gwibber.Service")
394
def LoadingStarted(self):
395
self.refreshRunning = True
397
@dbus.service.method("com.Gwibber.Service")
400
Calls the Gwibber Service to trigger a refresh operation
403
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
404
service = dbus.Interface(obj, "com.Gwibber.Service")
410
@dbus.service.method("com.Gwibber.Service", in_signature="s")
411
def PerformOp(self, opdata):
412
try: o = json.loads(opdata)
415
log.logger.debug("** Starting Single Operation **")
416
self.LoadingStarted()
418
params = ["account", "operation", "args", "transient"]
421
if "account" in o and self.collector.get_account(o["account"]):
422
account = self.collector.get_account(o["account"])
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)])
430
self.perform_async_operation(operation)
432
@dbus.service.method("com.Gwibber.Service", in_signature="s")
433
def UpdateIndicators(self, stream):
435
Update counts in messaging indicators
438
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
439
service = dbus.Interface(obj, "com.Gwibber.Service")
440
service.UpdateIndicators("stream")
442
self.handle_indicator_counts(stream)
444
@dbus.service.method("com.Gwibber.Service", in_signature="s")
445
def SendMessage(self, message):
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.
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")
455
self.send(list(self.collector.get_send_operations(message)))
457
@dbus.service.method("com.Gwibber.Service", in_signature="s")
458
def Send(self, opdata):
460
o = json.loads(opdata)
462
args = {"message": o["message"], "target": o["target"]}
463
operations = [(self.collector.get_account(o["target"]["account"]), "send_thread", args, None)]
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)
471
log.logger.error("Sending failed:\n%s", traceback.format_exc())
473
@dbus.service.method("com.Gwibber.Service", out_signature="s")
474
def GetServices(self):
476
Returns a list of services available as json string
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())
484
return json.dumps(SERVICES)
486
@dbus.service.method("com.Gwibber.Service", out_signature="s")
487
def GetFeatures(self):
489
Returns a list of features as json string
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())
496
return json.dumps(FEATURES)
498
@dbus.service.method("com.Gwibber.Service", out_signature="s")
499
def GetVersion(self):
501
Returns a the gwibber-service version as a string
504
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
505
service = dbus.Interface(obj, "com.Gwibber.Service")
506
version = service.GetVersion()
508
return VERSION_NUMBER
510
@dbus.service.method("com.Gwibber.Service")
516
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
517
service = dbus.Interface(obj, "com.Gwibber.Service")
520
log.logger.info("Gwibber Service is starting")
522
@dbus.service.method("com.Gwibber.Service")
528
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
529
service = dbus.Interface(obj, "com.Gwibber.Service")
532
log.logger.info("Gwibber Service is being shutdown")
535
@dbus.service.method("com.Gwibber.Service", out_signature="b")
536
def IndicatorInterestCheck(self):
538
Check for interest from the messaging menu indicator
542
obj = dbus.SessionBus().get_object("com.Gwibber.Service", "/com/gwibber/Service")
543
service = dbus.Interface(obj, "com.Gwibber.Service")
544
res = service.IndicatorInterestCheck()
546
if indicate and self.indicate:
547
return self.indicate.check_interest(indicate.INTEREST_SERVER_DISPLAY)
551
@dbus.service.signal("com.Gwibber.Service")
552
def IndicatorInterestAdded(self): pass
554
@dbus.service.signal("com.Gwibber.Service")
555
def IndicatorInterestRemoved(self): pass
557
@dbus.service.signal("com.Gwibber.Service", signature="s")
558
def Error(self, error): pass
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
565
def send_error_notify(self, error):
566
error = json.loads(error)["error"]
568
if self.notified_errors.has_key(error["account"]["service"]):
569
if self.notified_errors[error["account"]["service"]] == error["message"]:
572
icon = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % error["account"]["service"])
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"]
578
def perform_async_operation(self, iterable):
579
t = MapAsync(perform_operation, iterable, self.loading_complete, self.loading_failed, self.workerpool)
582
def loading_complete(self, output):
583
self.refresh_count += 1
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])
595
self.messages.Message("new", o2[-1])
599
with sqlite3.connect(SQLITE_DB_FILENAME) as db:
600
oldid = db.execute("select max(ROWID) from messages").fetchone()[0] or 0
602
output = db.executemany("INSERT OR REPLACE INTO messages (%s) VALUES (%s)" % (
603
",".join(self.messages.columns),
604
",".join("?" * len(self.messages.columns))), items)
606
for s in "messages", "replies", "private":
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)
613
self.update_indicators(self.unseen_counts)
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()
625
self.send_error_notify(error[1])
627
self.LoadingComplete()
628
log.logger.info("Loading complete: %s - %s", self.refresh_count, [o[0] for o in output])
630
def update_indicators(self, counts):
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"])
668
def on_indicator_interest_added(self, server, interest):
669
self.IndicatorInterestAdded()
671
def on_indicator_interest_removed(self, server, interest):
672
self.IndicatorInterestRemoved()
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")
679
client_obj = client_bus.get_object("com.GwibberClient",
680
"/com/GwibberClient", follow_name_owner_changes = True,
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())
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)
693
self.handle_indicator_counts(stream)
696
client_bus = dbus.SessionBus()
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())
705
def handle_focus_reply(self, *args):
706
log.logger.debug("Gwibber Client raised")
708
def handle_focus_error(self, *args):
709
log.logger.error("Failed to raise client %s", args)
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
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"])
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)
734
def handle_notify_item(self, message):
735
if SETTINGS["show_fullname"]:
736
sender_name = message["sender"].get("name", message["sender"].get("nick", ""))
738
sender_name = message["sender"].get("nick", message["sender"].get("name", ""))
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"])
754
image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"])
756
image = util.resources.get_ui_asset("icons/breakdance/scalable/%s.svg" % message["service"])
757
util.notify(sender_name, notify_text, image, 2000)
761
def loading_failed(self, exception, tb):
762
self.LoadingComplete()
763
log.logger.error("Loading failed: %s - %s", exception, tb)
765
def send(self, operations):
766
operations = util.compact(operations)
768
self.LoadingStarted()
769
log.logger.debug("*** Sending Message ***")
770
self.perform_async_operation(operations)
772
def refresh(self, *args):
773
if self.refresh_timer_id:
774
gobject.source_remove(self.refresh_timer_id)
776
if not self.maintRunning and not self.refreshRunning:
777
log.logger.debug("Refresh interval is set to %s", SETTINGS["interval"])
780
for o in self.collector.get_operations():
781
interval = FEATURES[o[1]].get("interval", 1)
782
if self.refresh_count % interval == 0:
786
log.logger.debug("** Starting Refresh - %s **", mx.DateTime.now())
787
self.LoadingStarted()
788
self.perform_async_operation(operations)
790
self.refresh_timer_id = gobject.timeout_add_seconds(int(60 * SETTINGS["interval"]), self.refresh)
792
self.refresh_timer_id = gobject.timeout_add_seconds(int(30), self.refresh)
794
if not self.maintDone:
795
self.maint_timer_id = gobject.timeout_add_seconds(60, self.do_maintenance)
799
class ConnectionMonitor(dbus.service.Object):
800
__dbus_object_path__ = "/com/gwibber/Connection"
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__)
807
self.sysbus = dbus.SystemBus()
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)
818
def on_connection_changed(self, state):
819
log.logger.debug("Network state changed, new state is %d", state)
823
NM_STATE_CONNECTING = 2
824
NM_STATE_CONNECTED = 3
825
NM_STATE_DISCONNECTED = 4
828
if state == NM_STATE_CONNECTED:
829
log.logger.info("Network state changed to Online")
830
self.ConnectionOnline()
832
log.logger.info("Network state changed to Offline")
833
self.ConnectionOffline()
835
@dbus.service.signal("com.Gwibber.Connection")
836
def ConnectionOnline(self): pass
838
@dbus.service.signal("com.Gwibber.Connection")
839
def ConnectionOffline(self): pass
841
@dbus.service.method("com.Gwibber.Connection")
842
def isConnected(self):
844
log.logger.info("Can't determine network state, assuming online")
847
if self.nm.state() == NM_STATE_CONNECTED:
854
class URLShorten(dbus.service.Object):
855
__dbus_object_path__ = "/com/gwibber/URLShorten"
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__)
862
@dbus.service.method("com.Gwibber.URLShorten", in_signature="s", out_signature="s")
863
def Shorten(self, url):
865
Takes a url as a string and returns a shortened url as a string.
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)
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
878
s = urlshorter.PROTOCOLS[service].URLShorter()
882
def IsShort(self, url):
883
for us in urlshorter.PROTOCOLS.values():
884
if url.startswith(us.PROTOCOL_INFO["fqdn"]):
888
class Translate(dbus.service.Object):
889
__dbus_object_path__ = "/com/gwibber/Translate"
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__)
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()
902
if data["responseStatus"] == 200:
903
return data.get("responseData", {}).get("translatedText", "")