Skip to content

Commit

Permalink
Merge branch 'master' into locales
Browse files Browse the repository at this point in the history
  • Loading branch information
kozec committed Nov 10, 2015
2 parents ae9caf2 + be18668 commit d1d2058
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 45 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Dependencies:
- [python-dateutil](http://labix.org/python-dateutil) (Python 2 version)
- [setuptools](https://pypi.python.org/pypi/setuptools)
- [psmisc](http://psmisc.sourceforge.net) (for the `killall` command)
- [Syncthing][syncthing] v0.11 or newer
- [Syncthing][syncthing] v0.12 or newer

Optional Dependencies:
- [pyinotify](https://github.com/seb-m/pyinotify/wiki) for instant synchronization.
Expand Down
30 changes: 30 additions & 0 deletions app.glade
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,29 @@
<property name="image">menu-popup-show-id-image</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu-popup-pause-device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">gtk-media-pause</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="activate" handler="cb_menu_popup_pause_device" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu-popup-resume-device">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">Resume</property>
<property name="image">menu-popup-resume-image</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
<property name="always_show_image">True</property>
<signal name="activate" handler="cb_menu_popup_resume_device" swapped="no"/>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="menu-popup-delete-device">
<property name="visible">True</property>
Expand Down Expand Up @@ -988,6 +1011,13 @@
<property name="can_focus">False</property>
<property name="icon-name">folder-open</property>
</object>

<object class="GtkImage" id="menu-popup-resume-image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="icon-name">gtk-media-play</property>
</object>

<!-- /Popup menu icon images -->

</interface>
36 changes: 31 additions & 5 deletions syncthing_gtk/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
log = logging.getLogger("App")

# Internal version used by updater (if enabled)
INTERNAL_VERSION = "v0.7"
INTERNAL_VERSION = "v0.8"
# Minimal Syncthing version supported by App
MIN_ST_VERSION = "0.11.0"
MIN_ST_VERSION = "0.12.0"

COLOR_DEVICE = "#707070" # Dark-gray
COLOR_DEVICE_SYNCING = "#2A89C8" # Blue
Expand Down Expand Up @@ -347,6 +347,8 @@ def setup_connection(self):
self.daemon.connect("last-seen-changed", self.cb_syncthing_last_seen_changed)
self.daemon.connect("device-connected", self.cb_syncthing_device_state_changed, True)
self.daemon.connect("device-disconnected", self.cb_syncthing_device_state_changed, False)
self.daemon.connect("device-paused", self.cb_syncthing_device_paused_resumed, True)
self.daemon.connect("device-resumed", self.cb_syncthing_device_paused_resumed, False)
self.daemon.connect("device-sync-started", self.cb_syncthing_device_sync_progress)
self.daemon.connect("device-sync-progress", self.cb_syncthing_device_sync_progress)
self.daemon.connect("device-sync-finished", self.cb_syncthing_device_sync_progress, 1.0)
Expand All @@ -357,6 +359,7 @@ def setup_connection(self):
self.daemon.connect("folder-sync-progress", self.cb_syncthing_folder_state_changed, COLOR_FOLDER_SYNCING, _("Syncing"))
self.daemon.connect("folder-sync-finished", self.cb_syncthing_folder_up_to_date)
self.daemon.connect("folder-scan-started", self.cb_syncthing_folder_state_changed, 1.0, COLOR_FOLDER_SCANNING, _("Scanning"))
self.daemon.connect("folder-scan-progress", self.cb_syncthing_folder_state_changed, COLOR_FOLDER_SCANNING, _("Scanning"))
self.daemon.connect("folder-scan-finished", self.cb_syncthing_folder_up_to_date)
self.daemon.connect("folder-stopped", self.cb_syncthing_folder_stopped)
self.daemon.connect("system-data-updated", self.cb_syncthing_system_data)
Expand Down Expand Up @@ -641,7 +644,7 @@ def cb_syncthing_con_error(self, daemon, reason, message, exception):
else:
self.display_run_daemon_dialog()
self.set_status(False)
elif reason == Daemon.OLD_VERSION and self.config["st_autoupdate"] and not self.process and not StDownloader is None:
elif reason == Daemon.OLD_VERSION and self.config["st_autoupdate"] and not self.process is None and not StDownloader is None:
# Daemon is too old, but autoupdater is enabled and I have control of deamon.
# Try to update.
from configuration import LONG_AGO
Expand Down Expand Up @@ -879,6 +882,19 @@ def cb_syncthing_last_seen_changed(self, daemon, nid, dt):
dtf = dt.strftime("%Y-%m-%d %H:%M")
device['last-seen'] = str(dtf)

def cb_syncthing_device_paused_resumed(self, daemon, nid, paused):
if nid in self.devices: # Should be always
device = self.devices[nid]
device.set_status(_("Paused") if paused else _("Disconnected"))
device.set_color_hex(COLOR_DEVICE_OFFLINE)
device["online"] = False
device["connected"] = False
# Update visible values
device.hide_values("sync", "inbps", "outbps", "version")
device.show_values("last-seen")
self.update_folders()
self.set_status(True)

def cb_syncthing_device_state_changed(self, daemon, nid, connected):
if nid in self.devices: # Should be always
device = self.devices[nid]
Expand Down Expand Up @@ -1646,6 +1662,8 @@ def cb_popup_menu_device(self, box, button, time):
b = box["id"] != self.daemon.get_my_id()
self["menu-popup-edit-device"].set_visible(b)
self["menu-popup-delete-device"].set_visible(b)
self["menu-popup-pause-device"].set_visible(box.get_status() != _("Paused"))
self["menu-popup-resume-device"].set_visible(box.get_status() == _("Paused"))
self["popup-menu-device"].popup(None, None, None, None, button, time)

def cb_menu_popup(self, source, menu):
Expand Down Expand Up @@ -1707,9 +1725,16 @@ def cb_menu_popup_override(self, *a):

def cb_menu_popup_delete_device(self, *a):
""" Handler for other 'edit' context menu item """
# Editing device
self.check_delete("device", self.rightclick_box["id"], self.rightclick_box.get_title())

def cb_menu_popup_pause_device(self, *a):
""" Handler for 'resume device' context menu item """
self.daemon.pause(self.rightclick_box["id"])

def cb_menu_popup_resume_device(self, *a):
""" Handler for 'resume device' context menu item """
self.daemon.resume(self.rightclick_box["id"])

def check_delete(self, mode, id, name):
"""
Asks user if he really wants to do what he just asked to do
Expand Down Expand Up @@ -1905,6 +1930,7 @@ def cb_wizard_finished(self, wizard, *a):
self.quit()

def cb_daemon_exit(self, proc, error_code):
print "cb_daemon_exit", proc, self.process
if proc == self.process:
# Whatever happens, if daemon dies while it shouldn't,
# restart it
Expand Down Expand Up @@ -1942,6 +1968,6 @@ def cb_daemon_startup_failed(self, proc, exception):
r = d.run()
d.destroy()
if r == FindDaemonDialog.RESPONSE_SAVED:
self.cb_daemon_exit(None, -1)
self.cb_daemon_exit(self.process, -1)
else:
self.quit()
107 changes: 73 additions & 34 deletions syncthing_gtk/daemon.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
log = logging.getLogger("Daemon")

# Minimal version supported by Daemon class
MIN_VERSION = "0.11"
MIN_VERSION = "0.12"

# Random constant used as key when adding headers to returned data in
# REST requests; Anything goes, as long as it isn't string
Expand Down Expand Up @@ -129,6 +129,14 @@ class Daemon(GObject.GObject, TimerManager):
id: id of device
last_seen: datetime object or None, if device was never seen
device-paused (id):
Emited when synchronization with device is paused
id: id of folder
device-resumed (id):
Emited when synchronization with device is resumed
id: id of folder
device-sync-started (id, progress):
Emited after device synchronization is started
id: id of folder
Expand Down Expand Up @@ -160,6 +168,11 @@ class Daemon(GObject.GObject, TimerManager):
daemon needs to be restarted
id: id of folder
folder-scan-progress (id, progress):
Emited repeatedly while folder is being scanned
id: id of folder
progress: scan progress (0.0 to 1.0)
folder-sync-progress (id, progress):
Emited repeatedly while folder is being synchronized
id: id of folder
Expand Down Expand Up @@ -228,18 +241,20 @@ class Daemon(GObject.GObject, TimerManager):
b"device-discovered" : (GObject.SIGNAL_RUN_FIRST, None, (object,object,)),
b"device-data-changed" : (GObject.SIGNAL_RUN_FIRST, None, (object, object, object, float, float, object, object)),
b"last-seen-changed" : (GObject.SIGNAL_RUN_FIRST, None, (object, object)),
b"device-paused" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"device-resumed" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"device-sync-started" : (GObject.SIGNAL_RUN_FIRST, None, (object, float)),
b"device-sync-progress" : (GObject.SIGNAL_RUN_FIRST, None, (object, float)),
b"device-sync-finished" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-added" : (GObject.SIGNAL_RUN_FIRST, None, (object, object)),
b"folder-data-changed" : (GObject.SIGNAL_RUN_FIRST, None, (object, object)),
b"folder-data-failed" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-sync-started" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-sync-finished" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-sync-progress" : (GObject.SIGNAL_RUN_FIRST, None, (object, float)),
b"folder-scan-started" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-sync-started" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-scan-finished" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-scan-progress" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-scan-started" : (GObject.SIGNAL_RUN_FIRST, None, (object,)),
b"folder-scan-progress" : (GObject.SIGNAL_RUN_FIRST, None, (object, float)),
b"folder-stopped" : (GObject.SIGNAL_RUN_FIRST, None, (object,object)),
b"item-started" : (GObject.SIGNAL_RUN_FIRST, None, (object,object,object)),
b"item-updated" : (GObject.SIGNAL_RUN_FIRST, None, (object,object,object)),
Expand Down Expand Up @@ -736,11 +751,12 @@ def _syncthing_cb_events(self, events):
self.timer("event", self._refresh_interval, self._request_events)

def _syncthing_cb_errors(self, errors):
for e in errors["errors"]:
t = parsetime(e["time"])
if t > self._last_error_time:
self.emit("error", e["error"])
self._last_error_time = t
if errors["errors"] is not None:
for e in errors["errors"]:
t = parsetime(e["time"])
if t > self._last_error_time:
self.emit("error", e["error"])
self._last_error_time = t
self.timer("errors", self._refresh_interval * 5, self._rest_request, "system/error", self._syncthing_cb_errors)

def _syncthing_cb_events_error(self, exception, command):
Expand Down Expand Up @@ -783,13 +799,23 @@ def _syncthing_cb_connections(self, data, prev_time):
cons[id]["outbps"] = 0.0
# Store updated device_data
for key in cons[id]:
if key != "clientVersion" or cons[id][key] != "": # Happens for 'total'
device_data[key] = cons[id][key]
if not key in ('clientVersion', 'connected'): # Don't want copy those
if cons[id][key] != "": # Happens for 'total'
device_data[key] = cons[id][key]

if "clientVersion" in cons[id] and cons[id]["clientVersion"] != "":
device_data["clientVersion"] = cons[id]["clientVersion"]

# Send "device-connected" signal, if device was disconnected until now
if not device_data["connected"] and nid != self._my_id:
device_data["connected"] = True
self.emit("device-connected", nid)
if cons[id]["paused"]:
# Send "device-paused" signal if device needed
device_data["connected"] = False
self.emit("device-paused", nid)
else:
# Send "device-connected" signal, if device was disconnected until now
if cons[id]["connected"]:
if not device_data["connected"] and nid != self._my_id:
device_data["connected"] = True
self.emit("device-connected", nid)
# Send "device-data-changed" signal
self.emit("device-data-changed", nid,
device_data["address"],
Expand Down Expand Up @@ -907,7 +933,8 @@ def _syncthing_cb_folder_data(self, data, rid):
if state in ('error', 'stopped'):
if not rid in self._stopped_folders:
self._stopped_folders.add(rid)
self.emit("folder-stopped", rid, data["invalid"])
reason = data["invalid"] or data["error"]
self.emit("folder-stopped", rid, reason)
self.emit('folder-data-changed', rid, data)
p = 0.0
if state == "syncing":
Expand Down Expand Up @@ -1003,9 +1030,7 @@ def _folder_state_changed(self, rid, state, progress):
self.emit("folder-sync-started", rid)
elif state == "scanning":
if not rid in self._stopped_folders:
if rid in self._scanning_folders:
self.emit("folder-scan-progress", rid)
else:
if not rid in self._scanning_folders:
self._scanning_folders.add(rid)
self.emit("folder-scan-started", rid)

Expand All @@ -1026,13 +1051,19 @@ def _on_event(self, e):
nid = e["data"]["id"]
self.emit("device-connected", nid)
elif eType == "DeviceDisconnected":
nid = e["data"]["id"]
self.emit("device-disconnected", nid)
self._request_last_seen()
nid = e["data"]["id"]
self.emit("device-disconnected", nid)
elif eType == "DeviceDiscovered":
nid = e["data"]["device"]
addresses = e["data"]["addrs"]
self.emit("device-discovered", nid, addresses)
elif eType == "DevicePaused":
nid = e["data"]["device"]
self.emit("device-paused", nid)
elif eType == "DeviceResumed":
nid = e["data"]["device"]
self.emit("device-resumed", nid)
self._request_last_seen()
elif eType == "FolderRejected":
nid = e["data"]["device"]
rid = e["data"]["folder"]
Expand All @@ -1041,6 +1072,13 @@ def _on_event(self, e):
nid = e["data"]["device"]
address = e["data"]["address"]
self.emit("device-rejected", nid, address)
elif eType == "FolderScanProgress":
rid = e["data"]["folder"]
total = float(e["data"]["total"])
if total > 0:
# ^^ just in case
status = float(e["data"]["current"]) / total
self.emit("folder-scan-progress", rid, status)
elif eType == "ItemStarted":
rid = e["data"]["folder"]
filename = e["data"]["item"]
Expand All @@ -1059,11 +1097,8 @@ def _on_event(self, e):
self.emit("item-updated", rid, filename, mtime)
elif eType == "ConfigSaved":
self.emit("config-saved")
elif eType == "ItemFinished":
# Not handled (yet?)
pass
elif eType == "DownloadProgress":
# Not handled (yet?)
elif eType in ("ItemFinished", "DownloadProgress", "RelayStateChanged"):
# Not handled
pass
else:
log.warning("Unhandled event type: %s", e)
Expand Down Expand Up @@ -1231,21 +1266,25 @@ def is_connected(self):
""" Returns True if daemon is known to be alive """
return self._connected

def pause(self, device_id):
""" Pauses synchronization with specified device """
self._rest_post("system/pause?device=%s" % (device_id,), {}, lambda *a: a, lambda *a: log.error(a), device_id)

def resume(self, device_id):
""" Resumes synchronization with specified device """
self._rest_post("system/resume?device=%s" % (device_id,), {}, lambda *a: a, lambda *a: log.error(a), device_id)

def rescan(self, folder_id, path=None):
""" Asks daemon to rescan entire folder or specified path """
def on_error(*a):
log.error(a)
if path is None:
self._rest_post("db/scan?folder=%s" % (folder_id,), {}, lambda *a: a, on_error, folder_id)
self._rest_post("db/scan?folder=%s" % (folder_id,), {}, lambda *a: a, lambda *a: log.error(a), folder_id)
else:
path_enc = urllib.quote(path.encode('utf-8'), ''.encode('utf-8'))
self._rest_post("db/scan?folder=%s&sub=%s" % (folder_id, path_enc), {}, lambda *a: a, on_error, folder_id)
self._rest_post("db/scan?folder=%s&sub=%s" % (folder_id, path_enc), {}, lambda *a: a, lambda *a: log.error(a), folder_id)

def override(self, folder_id):
""" Asks daemon to override changes made in specified folder """
def on_error(*a):
log.error(a)
self._rest_post("model/override?folder=%s" % (folder_id,), {}, lambda *a: a, on_error, folder_id)
self._rest_post("model/override?folder=%s" % (folder_id,), {}, lambda *a: a, lambda *a: log.error(a), folder_id)

def request_events(self):
"""
Expand Down
5 changes: 0 additions & 5 deletions syncthing_gtk/foldereditor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
log = logging.getLogger("FolderEditor")

COLOR_NEW = "#A0A0A0"
# Regexp to check if folder id is valid
RE_FOLDER_ID = re.compile("^([a-zA-Z0-9\-\._]{1,64})$")
# Regexp to generate folder id from filename
RE_GEN_ID = re.compile("([a-zA-Z0-9\-\._]{1,64}).*")
VALUES = [ "vid", "vpath", "vreadOnly", "vignorePerms", "vdevices",
Expand Down Expand Up @@ -254,9 +252,6 @@ def check_folder_id(self, value):
if value in self.app.folders:
# Duplicate folder id
return False
if RE_FOLDER_ID.match(value) is None:
# Invalid string
return False
return True

def check_path(self, value):
Expand Down

0 comments on commit d1d2058

Please sign in to comment.