diff --git a/GADGETS/dropbox/Makefile b/GADGETS/dropbox/Makefile new file mode 100644 index 0000000..50b1f27 --- /dev/null +++ b/GADGETS/dropbox/Makefile @@ -0,0 +1,36 @@ +# Simple Makefile for Enlightenment (edgar) gadgets + +# gadget specific config +GADGET_NAME = dropbox +EXTRA_FILES = + + +# nothing should be changed below this point +GADGET_FILES = __init__.pyc $(GADGET_NAME).edj +prefix = $(shell pkg-config --variable=libdir enlightenment) +gadget_folder = ${prefix}/enlightenment/gadgets/$(GADGET_NAME) + +.PHONY: all install clean + +all: $(GADGET_FILES) $(EXTRA_FILES) + +install: all + @mkdir -p -v ${gadget_folder} + @cp --preserve=mode -v $(GADGET_FILES) $(EXTRA_FILES) $(gadget_folder) + +uninstall: all + @rm -rfv ${gadget_folder} + +clean: + @rm -fv *.edj *.pyc + + +EDJE_CC = edje_cc +EDJE_FLAGS = -v -id images/ -fd fonts/ + +%.edj: %.edc images/* + $(EDJE_CC) $(EDJE_FLAGS) $< + @chmod -v og+r $@ + +%.pyc: %.py + python3 -c "from py_compile import compile; compile('$<', '$@')" diff --git a/GADGETS/dropbox/__init__.py b/GADGETS/dropbox/__init__.py new file mode 100644 index 0000000..fdfb415 --- /dev/null +++ b/GADGETS/dropbox/__init__.py @@ -0,0 +1,252 @@ +# This python file use the following encoding: utf-8 + +import os +import sys +import socket + +import e + +from efl import ecore +from efl import evas +from efl import edje +from efl.elementary.label import Label +from efl.elementary.entry import utf8_to_markup +from efl.elementary.button import Button + + +__gadget_name__ = 'Dropbox' +__gadget_vers__ = '0.1' +__gadget_auth__ = 'DaveMDS' +__gadget_mail__ = 'dave@gurumeditation.it' +__gadget_desc__ = 'Dropbox info gadget.' +__gadget_vapi__ = 1 +__gadget_opts__ = { 'popup_on_desktop': False } + + +#def DBG(msg): +# print("DB: " + msg) +# sys.stdout.flush() + + +class Gadget(e.Gadget): + + def __init__(self): + super().__init__() + self.db = Dropbox(self.db_status_changed_cb) + + def instance_created(self, obj, site): + super().instance_created(obj, site) + obj.size_hint_aspect = evas.EVAS_ASPECT_CONTROL_BOTH , 16, 16 + + def instance_destroyed(self, obj): + super().instance_destroyed(obj) + + def popup_created(self, popup): + super().popup_created(popup) + + popup.data['lb'] = Label(popup) + popup.part_box_append('popup.box', popup.data['lb']) + popup.data['lb'].show() + + popup.data['bt'] = Button(popup) + popup.data['bt'].callback_clicked_add(self.start_stop_clicked_cb) + popup.part_box_append('popup.box', popup.data['bt']) + popup.data['bt'].show() + + self.popup_update(popup) + + def popup_destroyed(self, popup): + super().popup_destroyed(popup) + + def db_status_changed_cb(self): + for icon in self._instances: + if self.db.is_running: + icon.signal_emit('daemon,running', '') + icon.signal_emit('state,'+self.db.status, '') + else: + icon.signal_emit('daemon,not_running', '') + icon.signal_emit('state,unwatched', '') + + for popup in self._popups: + self.popup_update(popup) + + def popup_update(self, popup): + if self.db.is_running: + popup.data['lb'].text = utf8_to_markup(self.db.status_msg) + popup.data['bt'].text = 'Stop Dropbox' + popup.data['bt'].disabled = False + elif self.db.is_installed: + popup.data['lb'].text = "Dropbox isn't running!" + popup.data['bt'].text = 'Start Dropbox' + popup.data['bt'].disabled = False + else: + popup.data['lb'].text = "Dropbox isn't installed!" + popup.data['bt'].text = 'Install Dropbox' + popup.data['bt'].disabled = True + + # force the popup to recalculate it's size + popup.size_hint_min = popup.size_min + + def start_stop_clicked_cb(self, btn): + if self.db.is_running: + self.db.stop() + else: + self.db.start() + + +class Dropbox(object): + def __init__(self, status_changed_cb=None): + self._status_changed_cb = status_changed_cb + + self.BASE_FOLDER = os.path.expanduser('~/Dropbox') + self.DAEMON = os.path.expanduser('~/.dropbox-dist/dropboxd') + self.PIDFILE = os.path.expanduser('~/.dropbox/dropbox.pid') + self.CMD_SOCKET = os.path.expanduser('~/.dropbox/command_socket') + + self._cmd_socket = None + self._cmd_fdh = None + self._reply_buffer = '' + self._status = '' + self._status_msg = '' + + self._connect_timer() + ecore.Timer(2.0, self._connect_timer) + ecore.Timer(2.0, self._fetch_status_timer) + + @property + def is_installed(self): + return os.path.exists(self.DAEMON) + + @property + def is_running(self): + """ Check if the dropbox daemon is running """ + try: + with open(self.PIDFILE, 'r') as f: + pid = int(f.read()) + with open('/proc/%d/cmdline' % pid, 'r') as f: + cmdline = f.read().lower() + except: + cmdline = '' + + return 'dropbox' in cmdline + + @property + def is_connected(self): + """ are we connected to the deamon socket ? """ + return self._cmd_fdh != None + + @property + def status(self): + """ 'up to date', 'syncing', 'unsyncable' or 'unwatched' """ + return self._status + + @property + def status_msg(self): + """ Long status message (more than one line) """ + return self._status_msg + + def start(self): + """ Start the dropbox daemon """ + ecore.Exe(self.DAEMON) + + def stop(self): + """ Stop the dropbox daemon """ + if self.is_connected: + cmd = 'tray_action_hard_exit\ndone\n' + try: + self._cmd_socket.sendall(cmd.encode('utf-8')) + except: + self._disconnect() + + def _connect_timer(self): + """ Try to connect to the daemon socket (if needed) """ + if self.is_connected: + return ecore.ECORE_CALLBACK_RENEW + + if not self.is_running: + return ecore.ECORE_CALLBACK_RENEW + + try: + self._cmd_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + self._cmd_socket.connect(self.CMD_SOCKET) + except: + self._cmd_socket = None + return ecore.ECORE_CALLBACK_RENEW + + self._cmd_fdh = ecore.FdHandler(self._cmd_socket, + ecore.ECORE_FD_READ | ecore.ECORE_FD_ERROR, + self._cmd_socket_data_available) + + return ecore.ECORE_CALLBACK_RENEW + + def _disconnect(self): + """ Disconnect from the daemon socket """ + self._cmd_fdh.delete() + self._cmd_fdh = None + self._cmd_socket.close() + self._cmd_socket = None + self._reply_buffer = '' + self._status = '' + self._status_msg = '' + + # alert the user that the state has changed + self._status_changed_cb() + + def _update_status(self, new_status): + if new_status == self._status: + return + self._status = new_status + + def _update_status_msg(self, new_status): + if new_status == self._status_msg: + return + self._status_msg = new_status + + # alert the user that the state has changed + self._status_changed_cb() + + def _cmd_socket_data_available(self, fdh): + if fdh.has_error(): + self._disconnect() + return ecore.ECORE_CALLBACK_CANCEL + + while True: + tmp = self._cmd_socket.recv(1024) + self._reply_buffer += tmp.decode('utf-8') + if len(tmp) < 1024: + break + + self._finalize_reply() + return ecore.ECORE_CALLBACK_RENEW + + def _finalize_reply(self): + reply = self._reply_buffer + if reply.endswith('done\n'): + self._reply_buffer = '' + + for cmd in reply.split('done\n'): + if not cmd: + continue + + cmd = cmd.split('\n') + if cmd[0] != 'ok': + return + if len(cmd) > 1 and cmd[1].startswith('status'): + status = cmd[1].split('\t') + if len(status) == 2 and status[1] in ('up to date', 'syncing', + 'unsyncable','unwatched'): + self._update_status(status[1]) + elif len(status) >= 2: + self._update_status_msg('\n'.join(status[1:])) + + def _fetch_status_timer(self): + if self.is_connected: + c1 = 'icon_overlay_file_status\npath\t%s\ndone\n' % self.BASE_FOLDER + c2 = 'get_dropbox_status\ndone\n' + try: + self._cmd_socket.sendall((c1 + c2).encode('utf-8')) + except: + self._disconnect() + + return ecore.ECORE_CALLBACK_RENEW + diff --git a/GADGETS/dropbox/docs/dropboxd-protocol b/GADGETS/dropbox/docs/dropboxd-protocol new file mode 100644 index 0000000..2fc8cd5 --- /dev/null +++ b/GADGETS/dropbox/docs/dropboxd-protocol @@ -0,0 +1,97 @@ + +Created by Steffen Schuldenzucker, 2010. If you know something important that I +missed, feel free to contact me: sschuldenzucker (at) uni-bonn (dot) de + +This is what I got out of the source code of nautilus-dropbox, dropbox.py and +dbcli.py. A big thanks to the authors of these tools. + +Protocol: +========= + +request: +-------- + + ... +... +done + + +reply (ok): +----------- +ok + ... +... +done + +reply (error): +-------------- +notok + +Delimiters for several items within a line (key and values above) are tabs. For +each key, any number of values may be given (including 0). Any number of +key-value-lines may be given (including 0) +Every message is terminated by a newline (after "done" or "notok", respectively) +Encoding is utf-8. +All paths mentioned have to be absolute (to "/"). + +Available Commands: +=================== +I only list replys for a successful command. TODO: can we get a more precise +error message? +A key may only appear once. If it appears more often here, these are +alternatives. + +<- get_dropbox_status # get deamon's status +-> status # if Idle + status + # ^ if something is happening. msg is a human-readable description + +<- get_public_link # see also the "copypublic" action below + path /file/in/Public # has to be a plain file, no folder +-> link http://... + +<- get_folder_tag /some/folder/ +-> tag shared # this folder is shared + tag dropbox # this is your dropbox's root folder + tag public # this is your public folder + tag photos # this is your photos folder + tag # otherwise + +<- icon_overlay_file_status # is the file up-to-date? + path /path/in/Dropbox +-> status up to date + status syncing + status unsyncable # TODO: when can this occur? + status unwatched + # ^ file is outside your Dropbox or is one of the ".dropbox" system files. + +# TODO: the name "paths" suggests that several files can be given. However, +# this is not done in nautilus-dropbox (and not seen elsewhere). +<- icon_overlay_context_options # get a list of available actions on this file + paths /path/in/Dropbox +-> options item1~desc1~action1 item2~desc2~action2 ... + # item: what is displayed in the menu + # desc: a tool tip for this item + # action: which verb to use to activate this option (see below) + +<- icon_overlay_context_action # perform some context action (see above) + verb # see below + paths /path/in/Dropbox +-> # no reply, except "ok" and "done" + +Where is one of + +# these open a page in your web browser +browse +revisions # only on plain files +share # only on folders +# these copy a http://... link to the clipboard +copypublic # only on plain files within the Public folder +copygallery # only on folders within the Photos folder + +NOTE that the clipboard (i.e. Ctrl-C / Ctrl-V) is something different than the +selection (select / middle mouse button). That took me some time today... + +<- tray_action_hard_exit # terminate the deamon +-> # NO reply. not even "ok". + diff --git a/GADGETS/dropbox/dropbox.edc b/GADGETS/dropbox/dropbox.edc new file mode 100644 index 0000000..b499e19 --- /dev/null +++ b/GADGETS/dropbox/dropbox.edc @@ -0,0 +1,98 @@ +/** + * EDGAR Dropbox Gadget + */ + + +images { + image: "dropbox.png" COMP; + image: "dropbox_gray.png" COMP; + image: "emblem-uptodate.png" COMP; + image: "emblem-syncing.png" COMP; + image: "emblem-unsyncable.png" COMP; +} + + +collections { +/** + * API [e/gadget/icon] The group used for the icon of the gadget + */ + group { name: "e/gadgets/dropbox/icon"; + parts { + part { name: "icon"; + description { + state: "default" 0.0; + aspect: 1.0 1.0; + aspect_preference: BOTH; + image { + normal: "dropbox.png"; + } + } + } + } + } + +/** + * API [e/gadget/main] The main group of the gadget + */ + group { name: "e/gadgets/dropbox/main"; + parts { + image { "icon"; + desc { "default"; + aspect: 1.0 1.0; aspect_preference: BOTH; + image.normal: "dropbox_gray.png"; + link.base: "daemon,not_running"; + } + desc { "running"; + aspect: 1.0 1.0; aspect_preference: BOTH; + image.normal: "dropbox.png"; + link.base: "daemon,running"; + } + } + image { "status"; + desc { "default"; + visible: 0; + rel1.relative: 0.6 0.6; + rel2.relative: 0.95 0.95; + image.normal: "emblem-uptodate.png"; + link.base: "state,unwatched"; + } + desc { "uptodate"; + inherit: "default"; + visible: 1; + image.normal: "emblem-uptodate.png"; + link.base: "state,up to date"; + } + desc { "syncing"; + inherit: "default"; + visible: 1; + image.normal: "emblem-syncing.png"; + link.base: "state,syncing"; + } + desc { "unsyncable"; + inherit: "default"; + visible: 1; + image.normal: "emblem-unsyncable.png"; + link.base: "state,unsyncable"; + } + } + } + } + +/** + * API [e/gadget/popup] This is the group that will be placed inside popups + */ + group { name: "e/gadgets/dropbox/popup"; + // min: 310 0; + parts { + box { "popup.box"; + desc { "default"; + box { + layout: "vertical"; + padding: 0 6; + min: 1 1; + } + } + } + } + } +} diff --git a/GADGETS/dropbox/images/dropbox.png b/GADGETS/dropbox/images/dropbox.png new file mode 100644 index 0000000..4450929 Binary files /dev/null and b/GADGETS/dropbox/images/dropbox.png differ diff --git a/GADGETS/dropbox/images/dropbox_gray.png b/GADGETS/dropbox/images/dropbox_gray.png new file mode 100644 index 0000000..8c50460 Binary files /dev/null and b/GADGETS/dropbox/images/dropbox_gray.png differ diff --git a/GADGETS/dropbox/images/emblem-syncing.png b/GADGETS/dropbox/images/emblem-syncing.png new file mode 100644 index 0000000..82c3d96 Binary files /dev/null and b/GADGETS/dropbox/images/emblem-syncing.png differ diff --git a/GADGETS/dropbox/images/emblem-unsyncable.png b/GADGETS/dropbox/images/emblem-unsyncable.png new file mode 100644 index 0000000..0f6704f Binary files /dev/null and b/GADGETS/dropbox/images/emblem-unsyncable.png differ diff --git a/GADGETS/dropbox/images/emblem-uptodate.png b/GADGETS/dropbox/images/emblem-uptodate.png new file mode 100644 index 0000000..d0e5a11 Binary files /dev/null and b/GADGETS/dropbox/images/emblem-uptodate.png differ