Add a new Dropbox gadget
It show the dropbox status and enable to start/stop the daemon
This commit is contained in:
parent
3f954f4675
commit
fb1afc286b
|
@ -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('$<', '$@')"
|
|
@ -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
|
||||||
|
|
|
@ -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:
|
||||||
|
--------
|
||||||
|
<command>
|
||||||
|
<key1> <value11> <value12> ...
|
||||||
|
...
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
reply (ok):
|
||||||
|
-----------
|
||||||
|
ok
|
||||||
|
<key1> <value11> <value12> ...
|
||||||
|
...
|
||||||
|
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 <msg>
|
||||||
|
# ^ 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 <action> # see below
|
||||||
|
paths /path/in/Dropbox
|
||||||
|
-> # no reply, except "ok" and "done"
|
||||||
|
|
||||||
|
Where <action> 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".
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
Loading…
Reference in New Issue