From c4d30ad31ae969c97e9f1c0c5393463a14179e06 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 24 Apr 2015 18:22:24 -0400 Subject: [PATCH 01/45] Reorder functions --- syncthing-ubuntu-indicator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 2d6a8a6..2ba31c9 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -260,6 +260,14 @@ def open_releases_page(self, *args): webbrowser.open('https://github.com/syncthing/syncthing/releases') + def rest_post(self, rest_path): + log.debug('rest_post {}'.format(rest_path)) + headers = {'X-API-Key': self.api_key} + if rest_path in ['/rest/system/restart', '/rest/system/shutdown']: + f = self.session.post(self.syncthing_url(rest_path), headers=headers) + return False + + def rest_get(self, rest_path): log.debug('rest_get {}'.format(rest_path)) # url for the included testserver: http://localhost:5115 @@ -697,14 +705,6 @@ def leave(self, widget): Gtk.main_quit() - def rest_post(self, rest_path): - log.debug('rest_post {}'.format(rest_path)) - headers = {'X-API-Key': self.api_key} - if rest_path in ['/rest/system/restart', '/rest/system/shutdown']: - f = self.session.post(self.syncthing_url(rest_path), headers=headers) - return False - - def timeout_rest(self): self.timeout_counter = (self.timeout_counter + 1) % 10 if self.rest_connected: From cc0242ab3b0eb2e913a31adc68c51195ecfb692e Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 24 Apr 2015 18:28:19 -0400 Subject: [PATCH 02/45] Reorder functions 2 --- syncthing-ubuntu-indicator.py | 58 +++++++++++++++++------------------ 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 2ba31c9..018ee60 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -487,6 +487,14 @@ def process_rest_ping(self, data): # end of the REST processing functions + def update(self): + for func in self.state: + if self.state[func]: + log.debug('self.update {}'.format(func)) + start = getattr(self, '%s' % func)() + return True + + def update_last_checked(self, isotime): #dt = dateutil.parser.parse(isotime) #self.last_checked_menu.set_label('Last checked: %s' % (dt.strftime('%H:%M'),)) @@ -534,27 +542,6 @@ def update_devices(self): self.state['update_devices'] = False - def update_title_menu(self): - self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( - self.syncthing_version, self.device_name)) - - - def count_connected(self): - return len([e for e in self.devices if e['state'] == 'connected']) - - - def syncthing_restart(self, *args): - self.rest_post('/rest/system/restart') - - - def syncthing_shutdown(self, *args): - self.rest_post('/rest/system/shutdown') - - - def convert_time(self, time): - return dateutil.parser.parse(time).strftime('%x %X') - - def update_files(self): self.current_files_menu.set_label(u'Syncing \u2191 %s \u2193 %s' % ( len(self.uploading_files), len(self.downloading_files))) @@ -626,6 +613,27 @@ def update_folders(self): self.state['update_folders'] = False + def update_title_menu(self): + self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( + self.syncthing_version, self.device_name)) + + + def count_connected(self): + return len([e for e in self.devices if e['state'] == 'connected']) + + + def syncthing_restart(self, *args): + self.rest_post('/rest/system/restart') + + + def syncthing_shutdown(self, *args): + self.rest_post('/rest/system/shutdown') + + + def convert_time(self, time): + return dateutil.parser.parse(time).strftime('%x %X') + + def calc_speed(self, old, new): return old / (new * 10) @@ -649,14 +657,6 @@ def show_about(self, widget): dialog.destroy() - def update(self): - for func in self.state: - if self.state[func]: - log.debug('self.update {}'.format(func)) - start = getattr(self, '%s' % func)() - return True - - def set_state(self, s=None): if not s: s = self.state['set_icon'] From ec1e1f9c6ba75b2afe9dc13c864d630400ffac35 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 24 Apr 2015 18:49:16 -0400 Subject: [PATCH 03/45] Cleanup set_state and folder_check_state functions Improve readability folder_check_state will not return 'unknown', so don't test for that --- syncthing-ubuntu-indicator.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 018ee60..cdd0c9d 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -228,7 +228,7 @@ def load_config_finish(self, fp, async_result): if elem.hasAttribute('id') and elem.hasAttribute('path'): self.folders.append({ 'folder': elem.getAttribute('id'), - 'directory': elem.getAttribute('path'), + 'directory': elem.getAttribute('path'), 'state': 'unknown', }) except: @@ -664,11 +664,7 @@ def set_state(self, s=None): if s == 'error': self.state['set_icon'] = s else: - rc = self.folder_check_state() - if rc != 'unknown': - self.state['set_icon'] = rc - else: - self.state['set_icon'] = s + self.state['set_icon'] = self.folder_check_state() def folder_check_state(self): @@ -678,11 +674,10 @@ def folder_check_state(self): if state['syncing'] > 0: return 'syncing' + elif state['scanning'] > 0 or state['cleaning'] > 0: + return 'scanning' else: - if state['scanning'] > 0 or state['cleaning'] > 0: - return 'scanning' - else: - return 'idle' + return 'idle' def set_icon(self): From 54ca13da30ed7e4d765cc8c387c16b66c05ffe1e Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 09:59:15 -0400 Subject: [PATCH 04/45] Check for errors reported from Syncthing --- syncthing-ubuntu-indicator.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index cdd0c9d..0e9a345 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -78,6 +78,10 @@ def create_menu(self): self.syncthing_upgrade_menu.connect('activate', self.open_releases_page) self.menu.append(self.syncthing_upgrade_menu) + self.mi_errors = Gtk.MenuItem('Errors: open web interface') + self.mi_errors.connect('activate', self.open_web_ui) + self.menu.append(self.mi_errors) + sep = Gtk.SeparatorMenuItem() sep.show() self.menu.append(sep) @@ -241,6 +245,7 @@ def load_config_finish(self, fp, async_result): GLib.idle_add(self.rest_get, '/rest/system/connections') GLib.idle_add(self.rest_get, '/rest/system/status') GLib.idle_add(self.rest_get, '/rest/system/upgrade') + GLib.idle_add(self.rest_get, '/rest/system/error') GLib.idle_add(self.rest_get, '/rest/events') GLib.timeout_add_seconds(TIMEOUT_GUI, self.update) GLib.timeout_add_seconds(TIMEOUT_REST, self.timeout_rest) @@ -421,6 +426,7 @@ def event_itemstarted(self, event): def event_itemfinished(self, event): + # TODO: test whether 'error' is null log.debug('item finished: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], 'file': event['data']['item'], @@ -484,6 +490,15 @@ def process_rest_ping(self, data): log.error('Syncthing v0.11.0-beta (or higher) required. Exiting.') self.leave() + + def process_rest_system_error(self, data): + if data['errors'] != []: + log.info('{}'.format(data['errors'])) + self.mi_errors.show() + self.set_state('error') + else: + self.mi_errors.hide() + # end of the REST processing functions @@ -705,6 +720,7 @@ def timeout_rest(self): if self.rest_connected: GLib.idle_add(self.rest_get, '/rest/system/connections') GLib.idle_add(self.rest_get, '/rest/system/status') + GLib.idle_add(self.rest_get, '/rest/system/error') if self.timeout_counter == 0: GLib.idle_add(self.rest_get, '/rest/system/upgrade') GLib.idle_add(self.rest_get, '/rest/system/version') From 7a08bf1d28d289ade2538f0b1c7fa26d8a8e4e03 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 10:15:18 -0400 Subject: [PATCH 05/45] Use 'id' and 'path' instead of 'folder' and 'directory' --- syncthing-ubuntu-indicator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 0e9a345..5af7cd1 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -231,8 +231,8 @@ def load_config_finish(self, fp, async_result): for elem in folders: if elem.hasAttribute('id') and elem.hasAttribute('path'): self.folders.append({ - 'folder': elem.getAttribute('id'), - 'directory': elem.getAttribute('path'), + 'id': elem.getAttribute('id'), + 'path': elem.getAttribute('path'), 'state': 'unknown', }) except: @@ -357,7 +357,7 @@ def event_unknown_event(self, event): def event_statechanged(self, event): for elem in self.folders: - if elem['folder'] == event['data']['folder']: + if elem['id'] == event['data']['folder']: elem['state'] = event['data']['to'] self.state['update_folders'] = True self.set_state() @@ -419,7 +419,7 @@ def event_itemstarted(self, event): 'direction': 'down'} self.downloading_files.append(file_details) for elm in self.folders: - if elm['folder'] == event['data']['folder']: + if elm['id'] == event['data']['folder']: elm['state'] = 'syncing' self.set_state() self.state['update_files'] = True @@ -613,16 +613,16 @@ def update_folders(self): if len(self.folders) == len(self.folder_menu_submenu): for mi in self.folder_menu_submenu: for elm in self.folders: - if str(mi.get_label()).split(' ', 1)[0] == elm['folder']: + if str(mi.get_label()).split(' ', 1)[0] == elm['id']: if elm['state'] in ['scanning', 'syncing']: - mi.set_label('{0} ({1})'.format(elm['folder'], elm['state'])) + mi.set_label('{0} ({1})'.format(elm['id'], elm['state'])) else: - mi.set_label(elm['folder']) + mi.set_label(elm['id']) else: for child in self.folder_menu_submenu.get_children(): self.folder_menu_submenu.remove(child) for elm in self.folders: - mi = Gtk.MenuItem(elm['folder']) + mi = Gtk.MenuItem(elm['id']) self.folder_menu_submenu.append(mi) mi.show() self.state['update_folders'] = False From 002283898a021cdeccd1915e0f60be6cde120f41 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 10:17:42 -0400 Subject: [PATCH 06/45] Replace connected_devices_menu with devices_menu Menu also shows devices that are disconnected --- syncthing-ubuntu-indicator.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 5af7cd1..2705ad5 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -86,12 +86,12 @@ def create_menu(self): sep.show() self.menu.append(sep) - self.connected_devices_menu = Gtk.MenuItem('Devices') - self.connected_devices_menu.show() - self.connected_devices_menu.set_sensitive(False) - self.menu.append(self.connected_devices_menu) - self.connected_devices_submenu = Gtk.Menu() - self.connected_devices_menu.set_submenu(self.connected_devices_submenu) + self.devices_menu = Gtk.MenuItem('Devices') + self.devices_menu.show() + self.devices_menu.set_sensitive(False) + self.menu.append(self.devices_menu) + self.devices_submenu = Gtk.Menu() + self.devices_menu.set_submenu(self.devices_submenu) self.folder_menu = Gtk.MenuItem('Folders') self.folder_menu.show() @@ -524,25 +524,25 @@ def update_last_seen_id(self, lsi): def update_devices(self): - self.connected_devices_menu.set_label('Devices (%s connected)' % self.count_connected()) + self.devices_menu.set_label('Devices (%s connected)' % self.count_connected()) if len(self.devices) == 0: - self.connected_devices_menu.set_label('Devices (0 connected)') - self.connected_devices_menu.set_sensitive(False) + self.devices_menu.set_label('Devices (0 connected)') + self.devices_menu.set_sensitive(False) else: - self.connected_devices_menu.set_sensitive(True) + self.devices_menu.set_sensitive(True) - if len(self.devices) == len(self.connected_devices_submenu) + 1: - # this updates the connected devices menu - for mi in self.connected_devices_submenu: + if len(self.devices) == len(self.devices_submenu) + 1: + # this updates the devices menu + for mi in self.devices_submenu: for elm in self.devices: if mi.get_label() == elm['name']: mi.set_label(elm['name']) mi.set_sensitive(elm['state'] == 'connected') else: - # this populates the connected devices menu with devices from config - for child in self.connected_devices_submenu.get_children(): - self.connected_devices_submenu.remove(child) + # this populates the devices menu with devices from config + for child in self.devices_submenu.get_children(): + self.devices_submenu.remove(child) for nid in sorted(self.devices, key=lambda nid: nid['name']): if nid['id'] == self.system_data.get('myID', None): @@ -552,7 +552,7 @@ def update_devices(self): mi = Gtk.MenuItem(nid['name']) mi.set_sensitive(nid['state'] == 'connected') - self.connected_devices_submenu.append(mi) + self.devices_submenu.append(mi) mi.show() self.state['update_devices'] = False From 58106b90ba36a50976b2b3ad29e230eeb91a6894 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 12:54:35 -0400 Subject: [PATCH 07/45] Add feature to start Syncthing from indicator Syncthing can now be started, restarted, and shutdown from indicator menu. Example use case: on a laptop, shutdown Syncthing when on a public network, and startup again when at home. Edit start-syncthing.sh with the path to Syncthing. Syncthing will continue running if indicator exits. --- start-syncthing.sh | 12 +++++++ syncthing-ubuntu-indicator.py | 68 ++++++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 17 deletions(-) create mode 100755 start-syncthing.sh diff --git a/start-syncthing.sh b/start-syncthing.sh new file mode 100755 index 0000000..c35c8cf --- /dev/null +++ b/start-syncthing.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +## Uncomment one of the commands below and edit it with your Syncthing install path. +## For more information try: +## syncthing -help +## https://github.com/syncthing/syncthing/wiki +## https://forum.syncthing.net/c/howto + +## Example commands: +# /path/to/syncthing -no-browser & +# nice -n 19 ionice -c3 /path/to/syncthing -no-browser & +# GOMAXPROCS=1 nice -n 19 ionice -c3 /path/to/syncthing -no-browser & diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 2705ad5..5b33c52 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -7,6 +7,7 @@ import json import logging as log import os +import subprocess import sys import urlparse import webbrowser @@ -35,7 +36,7 @@ def __init__(self): self.state = {'update_folders': True, 'update_devices': True, 'update_files': True, - 'update_title_menu': False, + 'update_st_running': False, 'set_icon': 'idle'} self.set_icon() self.create_menu() @@ -126,15 +127,23 @@ def create_menu(self): self.more_submenu = Gtk.Menu() self.more_menu.set_submenu(self.more_submenu) - mi_restart_syncthing = Gtk.MenuItem('Restart Syncthing') - mi_restart_syncthing.connect('activate', self.syncthing_restart) - mi_restart_syncthing.show() - self.more_submenu.append(mi_restart_syncthing) + self.mi_start_syncthing = Gtk.MenuItem('Start Syncthing') + self.mi_start_syncthing.connect('activate', self.syncthing_start) + self.mi_start_syncthing.set_sensitive(False) + self.mi_start_syncthing.show() + self.more_submenu.append(self.mi_start_syncthing) - mi_shutdown_syncthing = Gtk.MenuItem('Shutdown Syncthing') - mi_shutdown_syncthing.connect('activate', self.syncthing_shutdown) - mi_shutdown_syncthing.show() - self.more_submenu.append(mi_shutdown_syncthing) + self.mi_restart_syncthing = Gtk.MenuItem('Restart Syncthing') + self.mi_restart_syncthing.connect('activate', self.syncthing_restart) + self.mi_restart_syncthing.set_sensitive(False) + self.mi_restart_syncthing.show() + self.more_submenu.append(self.mi_restart_syncthing) + + self.mi_shutdown_syncthing = Gtk.MenuItem('Shutdown Syncthing') + self.mi_shutdown_syncthing.connect('activate', self.syncthing_shutdown) + self.mi_shutdown_syncthing.set_sensitive(False) + self.mi_shutdown_syncthing.show() + self.more_submenu.append(self.mi_shutdown_syncthing) sep = Gtk.SeparatorMenuItem() sep.show() @@ -295,6 +304,7 @@ def rest_receive_data(self, future): log.error("Couldn't connect to Syncthing REST interface at {}".format( self.syncthing_url(''))) self.rest_connected = False + self.state['update_st_running'] = True if self.ping_counter > 1: self.set_state('error') return @@ -458,7 +468,7 @@ def process_rest_system_connections(self, data): def process_rest_system_status(self, data): self.system_data = data - self.state['update_title_menu'] = True + self.state['update_st_running'] = True def process_rest_system_upgrade(self, data): @@ -472,7 +482,7 @@ def process_rest_system_upgrade(self, data): def process_rest_system_version(self, data): self.syncthing_version = data['version'] - self.state['update_title_menu'] = True + self.state['update_st_running'] = True def process_rest_system_ping(self, data): @@ -519,8 +529,9 @@ def update_last_checked(self, isotime): def update_last_seen_id(self, lsi): if lsi > self.last_seen_id: self.last_seen_id = lsi - else: - log.warning('received event id less than last_seen_id') + elif lsi < self.last_seen_id: + log.warning('received event id {} less than last_seen_id {}'.format( + lsi, self.last_seen_id)) def update_devices(self): @@ -547,7 +558,7 @@ def update_devices(self): for nid in sorted(self.devices, key=lambda nid: nid['name']): if nid['id'] == self.system_data.get('myID', None): self.device_name = nid['name'] - self.update_title_menu() + self.state['update_st_running'] = True continue mi = Gtk.MenuItem(nid['name']) @@ -628,21 +639,44 @@ def update_folders(self): self.state['update_folders'] = False - def update_title_menu(self): - self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( - self.syncthing_version, self.device_name)) + def update_st_running(self): + if self.rest_connected: + self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( + self.syncthing_version, self.device_name)) + self.mi_start_syncthing.set_sensitive(False) + self.mi_restart_syncthing.set_sensitive(True) + self.mi_shutdown_syncthing.set_sensitive(True) + else: + self.title_menu.set_label('Syncthing: not running?') + self.mi_start_syncthing.set_sensitive(True) + self.mi_restart_syncthing.set_sensitive(False) + self.mi_shutdown_syncthing.set_sensitive(False) def count_connected(self): return len([e for e in self.devices if e['state'] == 'connected']) + def syncthing_start(self, *args): + cmd = os.path.join(self.wd, 'start-syncthing.sh') + log.info('Starting {}'.format(cmd)) + try: + proc = subprocess.Popen([cmd]) + except Exception as e: + log.error("Couldn't run {}: {}".format(cmd, e)) + return + self.state['update_st_running'] = True + self.last_seen_id = int(0) + + def syncthing_restart(self, *args): self.rest_post('/rest/system/restart') + self.last_seen_id = int(0) def syncthing_shutdown(self, *args): self.rest_post('/rest/system/shutdown') + self.last_seen_id = int(0) def convert_time(self, time): From 9eee616395821ec6c104d760af6abec35f3099dd Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 13:51:03 -0400 Subject: [PATCH 08/45] Pad strings to avoid changes in folder menu width Try to avoid ugly gaps between menus --- syncthing-ubuntu-indicator.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 5b33c52..3130e46 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -621,19 +621,22 @@ def update_folders(self): self.folder_menu.set_sensitive(False) else: self.folder_menu.set_sensitive(True) + folder_maxlength = 0 if len(self.folders) == len(self.folder_menu_submenu): for mi in self.folder_menu_submenu: for elm in self.folders: + folder_maxlength = max(folder_maxlength, len(elm['id'])) if str(mi.get_label()).split(' ', 1)[0] == elm['id']: if elm['state'] in ['scanning', 'syncing']: mi.set_label('{0} ({1})'.format(elm['id'], elm['state'])) else: - mi.set_label(elm['id']) + mi.set_label(elm['id'].ljust(folder_maxlength + 20)) else: for child in self.folder_menu_submenu.get_children(): self.folder_menu_submenu.remove(child) for elm in self.folders: - mi = Gtk.MenuItem(elm['id']) + folder_maxlength = max(folder_maxlength, len(elm['id'])) + mi = Gtk.MenuItem(elm['id'].ljust(folder_maxlength + 20)) self.folder_menu_submenu.append(mi) mi.show() self.state['update_folders'] = False From bc7749c11f327a52678593ccd00ccca1865b96ed Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 25 Apr 2015 22:33:26 -0400 Subject: [PATCH 09/45] Fix Unicode error --- syncthing-ubuntu-indicator.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 3130e46..c28f077 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -338,8 +338,9 @@ def rest_receive_data(self, future): try: for qitem in json_data: self.process_event(qitem) - except ValueError: - log.warning('rest_receive_data: error parsing json in /rest/events') + except ValueError as e: + log.warning('rest_receive_data: error processing event ({})'.format(e)) + log.debug(qitem) self.set_state('error') else: fn = getattr( @@ -423,7 +424,7 @@ def event_devicedisconnected(self, event): def event_itemstarted(self, event): - log.debug('item started: {}'.format(event['data']['item'])) + log.debug(u'item started: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], 'file': event['data']['item'], 'direction': 'down'} @@ -437,7 +438,7 @@ def event_itemstarted(self, event): def event_itemfinished(self, event): # TODO: test whether 'error' is null - log.debug('item finished: {}'.format(event['data']['item'])) + log.debug(u'item finished: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], 'file': event['data']['item'], 'direction': 'down'} From 525d36d6fd90f5c0716b61291eacc9bec2e055ba Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 20:33:49 -0400 Subject: [PATCH 10/45] Use working directory when loading license --- syncthing-ubuntu-indicator.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index c28f077..0e603ee 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -692,9 +692,9 @@ def calc_speed(self, old, new): def license(self): - with open('LICENSE', 'r') as f: - license = f.read() - return license + with open(os.path.join(self.wd, 'LICENSE'), 'r') as f: + lic = f.read() + return lic def show_about(self, widget): From dd1228c14c38b5c7bb3b548d9a03246793c19a47 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 20:40:02 -0400 Subject: [PATCH 11/45] Process FolderSummary events Allows us to know number of files out of sync (needFiles) --- syncthing-ubuntu-indicator.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 0e603ee..9564a8f 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -373,6 +373,11 @@ def event_statechanged(self, event): self.state['update_folders'] = True self.set_state() + def event_foldersummary(self, event): + for elem in self.folders: + if elem['id'] == event['data']['folder']: + elem.update(event['data']['summary']) + self.state['update_folders'] = True def event_starting(self, event): self.set_state('paused') From 0473294d76a43d5d5b204afbbeabdfdee7af1fbb Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 21:20:07 -0400 Subject: [PATCH 12/45] Catch more timeout exceptions --- syncthing-ubuntu-indicator.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 9564a8f..430f725 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -13,7 +13,8 @@ import webbrowser import pytz -import requests +import requests # used only to catch exceptions +import socket # used only to catch exceptions from requests_futures.sessions import FuturesSession from gi.repository import Gtk, Gio, GLib from gi.repository import AppIndicator3 as appindicator @@ -308,8 +309,8 @@ def rest_receive_data(self, future): if self.ping_counter > 1: self.set_state('error') return - except requests.exceptions.Timeout: - log.warning('Connection timeout') + except (requests.exceptions.Timeout, socket.timeout): + log.warning('Timeout') return except Exception as e: log.error('exception: {}'.format(e)) From 67cbf29386f440ca72d58e5f26b15840f94da378 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 21:56:51 -0400 Subject: [PATCH 13/45] Keep error icon until errors are cleared from web interface --- syncthing-ubuntu-indicator.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 430f725..e42ccc5 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -47,6 +47,8 @@ def __init__(self): self.recent_files = [] self.folders = [] self.devices = [] + self.errors = [] + self.last_ping = None self.system_data = {} self.syncthing_base = 'http://localhost:8080' @@ -509,7 +511,8 @@ def process_rest_ping(self, data): def process_rest_system_error(self, data): - if data['errors'] != []: + self.errors = data['errors'] + if self.errors: log.info('{}'.format(data['errors'])) self.mi_errors.show() self.set_state('error') @@ -720,8 +723,8 @@ def set_state(self, s=None): if not s: s = self.state['set_icon'] - if s == 'error': - self.state['set_icon'] = s + if (s == 'error') or self.errors: + self.state['set_icon'] = 'error' else: self.state['set_icon'] = self.folder_check_state() From 0a963133dd868bf2988d20491cea92e08f9b8fc2 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 22:03:54 -0400 Subject: [PATCH 14/45] Show 'paused' icon when Syncthing is not running --- syncthing-ubuntu-indicator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index e42ccc5..d99e5ae 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -38,7 +38,7 @@ def __init__(self): 'update_devices': True, 'update_files': True, 'update_st_running': False, - 'set_icon': 'idle'} + 'set_icon': 'paused'} self.set_icon() self.create_menu() @@ -309,7 +309,7 @@ def rest_receive_data(self, future): self.rest_connected = False self.state['update_st_running'] = True if self.ping_counter > 1: - self.set_state('error') + self.set_state('paused') return except (requests.exceptions.Timeout, socket.timeout): log.warning('Timeout') @@ -725,6 +725,8 @@ def set_state(self, s=None): if (s == 'error') or self.errors: self.state['set_icon'] = 'error' + elif not self.rest_connected: + self.state['set_icon'] = 'paused' else: self.state['set_icon'] = self.folder_check_state() From b95cf90617bc416edef513cd5babcb6c5b48d343 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 22:05:20 -0400 Subject: [PATCH 15/45] Suppress log messages coming from urllib3 --- syncthing-ubuntu-indicator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index d99e5ae..ac3f799 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -805,9 +805,12 @@ def timeout_events(self): TIMEOUT_REST = args.timeout_rest TIMEOUT_GUI = args.timeout_gui - # setup debugging: + # Setup logging: loglevels = {'debug': log.DEBUG, 'info': log.INFO, 'warning': log.WARNING, 'error': log.ERROR} log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=loglevels[args.loglevel]) + requests_log = log.getLogger('urllib3.connectionpool') + requests_log.setLevel(log.WARNING) + requests_log.propagate = True app = Main() Gtk.main() From 92acf64475864a4ba2c4bfbf4612cff2101a9c08 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 22:10:48 -0400 Subject: [PATCH 16/45] Show number of syncing files in folder submenu --- syncthing-ubuntu-indicator.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index ac3f799..83f3c7f 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -627,9 +627,7 @@ def update_files(self): def update_folders(self): - if len(self.folders) == 0: - self.folder_menu.set_sensitive(False) - else: + if self.folders: self.folder_menu.set_sensitive(True) folder_maxlength = 0 if len(self.folders) == len(self.folder_menu_submenu): @@ -637,8 +635,17 @@ def update_folders(self): for elm in self.folders: folder_maxlength = max(folder_maxlength, len(elm['id'])) if str(mi.get_label()).split(' ', 1)[0] == elm['id']: - if elm['state'] in ['scanning', 'syncing']: - mi.set_label('{0} ({1})'.format(elm['id'], elm['state'])) + if elm['state'] == 'scanning': + mi.set_label('{} (scanning)'.format(elm['id'])) + elif elm['state'] == 'syncing': + if elm.get('needFiles') > 1: + lbltext = '{fid} (syncing {num} files)' + elif elm.get('needFiles') == 1: + lbltext = '{fid} (syncing {num} file)' + else: + lbltext = '{fid} (syncing)' + mi.set_label(lbltext.format( + fid=elm['id'], num=elm.get('needFiles'))) else: mi.set_label(elm['id'].ljust(folder_maxlength + 20)) else: @@ -649,6 +656,8 @@ def update_folders(self): mi = Gtk.MenuItem(elm['id'].ljust(folder_maxlength + 20)) self.folder_menu_submenu.append(mi) mi.show() + else: + self.folder_menu.set_sensitive(False) self.state['update_folders'] = False From 321f56f75c7ee88d1790ba388e822ece92568ec7 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 22:19:38 -0400 Subject: [PATCH 17/45] Update version error message; reduce whitespace --- syncthing-ubuntu-indicator.py | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 83f3c7f..36b6f85 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -177,7 +177,6 @@ def load_config_begin(self): f.load_contents_async(None, self.load_config_finish) return False - def load_config_finish(self, fp, async_result): try: success, data, etag = fp.load_contents_finish(async_result) @@ -284,7 +283,6 @@ def rest_post(self, rest_path): f = self.session.post(self.syncthing_url(rest_path), headers=headers) return False - def rest_get(self, rest_path): log.debug('rest_get {}'.format(rest_path)) # url for the included testserver: http://localhost:5115 @@ -299,7 +297,6 @@ def rest_get(self, rest_path): f.add_done_callback(self.rest_receive_data) return False - def rest_receive_data(self, future): try: r = future.result() @@ -364,11 +361,9 @@ def process_event(self, event): fn = getattr(self, 'event_{}'.format(t), self.event_unknown_event)(event) self.update_last_seen_id(event.get('id', 0)) - def event_unknown_event(self, event): pass - def event_statechanged(self, event): for elem in self.folders: if elem['id'] == event['data']['folder']: @@ -386,18 +381,15 @@ def event_starting(self, event): self.set_state('paused') log.info('Received that Syncthing was starting at %s' % event['time']) - def event_startupcomplete(self, event): self.set_state('idle') time = self.convert_time(event['time']) log.debug('Startup done at %s' % time) - def event_ping(self, event): self.last_ping = dateutil.parser.parse(event['time']) log.debug('A ping was sent at %s' % self.last_ping.strftime('%H:%M')) - def event_devicediscovered(self, event): found = False for elm in self.devices: @@ -414,7 +406,6 @@ def event_devicediscovered(self, event): }) self.state['update_devices'] = True - def event_deviceconnected(self, event): for elem in self.devices: if event['data']['id'] == elem['id']: @@ -422,7 +413,6 @@ def event_deviceconnected(self, event): log.debug('device %s connected' % elem['name']) self.state['update_devices'] = True - def event_devicedisconnected(self, event): for elem in self.devices: if event['data']['id'] == elem['id']: @@ -430,7 +420,6 @@ def event_devicedisconnected(self, event): log.debug('device %s disconnected' % elem['name']) self.state['update_devices'] = True - def event_itemstarted(self, event): log.debug(u'item started: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], @@ -443,7 +432,6 @@ def event_itemstarted(self, event): self.set_state() self.state['update_files'] = True - def event_itemfinished(self, event): # TODO: test whether 'error' is null log.debug(u'item finished: {}'.format(event['data']['item'])) @@ -464,7 +452,6 @@ def event_itemfinished(self, event): # end of the event processing dings - # begin REST processing functions def process_rest_system_connections(self, data): @@ -474,12 +461,10 @@ def process_rest_system_connections(self, data): nid['state'] = 'connected' self.state['update_devices'] = True - def process_rest_system_status(self, data): self.system_data = data self.state['update_st_running'] = True - def process_rest_system_upgrade(self, data): if data['newer']: self.syncthing_upgrade_menu.set_label( @@ -488,12 +473,10 @@ def process_rest_system_upgrade(self, data): else: self.syncthing_upgrade_menu.hide() - def process_rest_system_version(self, data): self.syncthing_version = data['version'] self.state['update_st_running'] = True - def process_rest_system_ping(self, data): if data['ping'] == 'pong': log.info('Connected to Syncthing REST interface at {}'.format( @@ -501,15 +484,13 @@ def process_rest_system_ping(self, data): self.rest_connected = True self.ping_counter = 0 - def process_rest_ping(self, data): if data['ping'] == 'pong': # Basic version check log.error('Detected running Syncthing version < v0.11') - log.error('Syncthing v0.11.0-beta (or higher) required. Exiting.') + log.error('Syncthing v0.11 (or higher) required. Exiting.') self.leave() - def process_rest_system_error(self, data): self.errors = data['errors'] if self.errors: @@ -529,13 +510,11 @@ def update(self): start = getattr(self, '%s' % func)() return True - def update_last_checked(self, isotime): #dt = dateutil.parser.parse(isotime) #self.last_checked_menu.set_label('Last checked: %s' % (dt.strftime('%H:%M'),)) pass - def update_last_seen_id(self, lsi): if lsi > self.last_seen_id: self.last_seen_id = lsi @@ -543,7 +522,6 @@ def update_last_seen_id(self, lsi): log.warning('received event id {} less than last_seen_id {}'.format( lsi, self.last_seen_id)) - def update_devices(self): self.devices_menu.set_label('Devices (%s connected)' % self.count_connected()) if len(self.devices) == 0: @@ -577,7 +555,6 @@ def update_devices(self): mi.show() self.state['update_devices'] = False - def update_files(self): self.current_files_menu.set_label(u'Syncing \u2191 %s \u2193 %s' % ( len(self.uploading_files), len(self.downloading_files))) @@ -625,7 +602,6 @@ def update_files(self): self.recent_files_menu.show() self.state['update_files'] = False - def update_folders(self): if self.folders: self.folder_menu.set_sensitive(True) @@ -660,7 +636,6 @@ def update_folders(self): self.folder_menu.set_sensitive(False) self.state['update_folders'] = False - def update_st_running(self): if self.rest_connected: self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( @@ -690,12 +665,10 @@ def syncthing_start(self, *args): self.state['update_st_running'] = True self.last_seen_id = int(0) - def syncthing_restart(self, *args): self.rest_post('/rest/system/restart') self.last_seen_id = int(0) - def syncthing_shutdown(self, *args): self.rest_post('/rest/system/shutdown') self.last_seen_id = int(0) @@ -704,7 +677,6 @@ def syncthing_shutdown(self, *args): def convert_time(self, time): return dateutil.parser.parse(time).strftime('%x %X') - def calc_speed(self, old, new): return old / (new * 10) @@ -714,7 +686,6 @@ def license(self): lic = f.read() return lic - def show_about(self, widget): dialog = Gtk.AboutDialog() dialog.set_default_icon_from_file(os.path.join(self.icon_path, 'syncthing-client-idle.svg')) From 0ead08a9c2b10989c13a57bf3472181042bbd090 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 23:09:40 -0400 Subject: [PATCH 18/45] Avoid KeyError when checking folder state --- syncthing-ubuntu-indicator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 36b6f85..f960337 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -712,9 +712,11 @@ def set_state(self, s=None): def folder_check_state(self): - state = {'syncing': 0, 'idle': 0, 'cleaning': 0, 'scanning': 0, 'unknown': 0} + state = {'syncing': 0, 'idle': 0, 'cleaning': 0, 'scanning': 0, + 'unknown': 0} for elem in self.folders: - state[elem['state']] += 1 + if elem['state'] in state: + state[elem['state']] += 1 if state['syncing'] > 0: return 'syncing' From 9efa64ca92d607a24921e203c4a0111bcbf704fe Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Fri, 8 May 2015 23:12:14 -0400 Subject: [PATCH 19/45] Wrap long lines --- syncthing-ubuntu-indicator.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index f960337..d445a4a 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -738,7 +738,8 @@ def set_icon(self): } self.ind.set_attention_icon(icon[self.state['set_icon']]['name']) - self.ind.set_icon_full(icon[self.state['set_icon']]['name'], icon[self.state['set_icon']]['descr']) + self.ind.set_icon_full(icon[self.state['set_icon']]['name'], + icon[self.state['set_icon']]['descr']) #GLib.timeout_add_seconds(1, self.set_icon) @@ -788,8 +789,10 @@ def timeout_events(self): TIMEOUT_GUI = args.timeout_gui # Setup logging: - loglevels = {'debug': log.DEBUG, 'info': log.INFO, 'warning': log.WARNING, 'error': log.ERROR} - log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=loglevels[args.loglevel]) + loglevels = {'debug': log.DEBUG, 'info': log.INFO, + 'warning': log.WARNING, 'error': log.ERROR} + log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', + level=loglevels[args.loglevel]) requests_log = log.getLogger('urllib3.connectionpool') requests_log.setLevel(log.WARNING) requests_log.propagate = True From 536bf794e3353258763bdda8a4dc9d4a265a613c Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 13:08:04 -0400 Subject: [PATCH 20/45] Update device connected/disconnected messages --- syncthing-ubuntu-indicator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index d445a4a..d9bc0cb 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -410,14 +410,14 @@ def event_deviceconnected(self, event): for elem in self.devices: if event['data']['id'] == elem['id']: elem['state'] = 'connected' - log.debug('device %s connected' % elem['name']) + log.info('Device connected: %s' % elem['name']) self.state['update_devices'] = True def event_devicedisconnected(self, event): for elem in self.devices: if event['data']['id'] == elem['id']: elem['state'] = 'disconnected' - log.debug('device %s disconnected' % elem['name']) + log.info('Device disconnected: %s' % elem['name']) self.state['update_devices'] = True def event_itemstarted(self, event): From 0e303f56e184f2ca0954e844d1fd76a88c9d6f89 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 13:08:53 -0400 Subject: [PATCH 21/45] Handle more exceptions in event processing --- syncthing-ubuntu-indicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index d9bc0cb..cd371d2 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -338,7 +338,7 @@ def rest_receive_data(self, future): try: for qitem in json_data: self.process_event(qitem) - except ValueError as e: + except Exception as e: log.warning('rest_receive_data: error processing event ({})'.format(e)) log.debug(qitem) self.set_state('error') From e5ec2b7beffccf372b19476b3adddd4490399d52 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 13:17:11 -0400 Subject: [PATCH 22/45] Use 0 instead of int(0) --- syncthing-ubuntu-indicator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index cd371d2..4f6daf6 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -54,7 +54,7 @@ def __init__(self): self.syncthing_base = 'http://localhost:8080' self.syncthing_version = '' self.device_name = '' - self.last_seen_id = int(0) + self.last_seen_id = 0 self.rest_connected = False self.timeout_counter = 0 self.ping_counter = 0 @@ -663,15 +663,15 @@ def syncthing_start(self, *args): log.error("Couldn't run {}: {}".format(cmd, e)) return self.state['update_st_running'] = True - self.last_seen_id = int(0) + self.last_seen_id = 0 def syncthing_restart(self, *args): self.rest_post('/rest/system/restart') - self.last_seen_id = int(0) + self.last_seen_id = 0 def syncthing_shutdown(self, *args): self.rest_post('/rest/system/shutdown') - self.last_seen_id = int(0) + self.last_seen_id = 0 def convert_time(self, time): From 7525aa559cf436353345eb5dee086e2706d2cbb7 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 13:39:32 -0400 Subject: [PATCH 23/45] Increase number of recent files shown in menu --- syncthing-ubuntu-indicator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 4f6daf6..79f5503 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -447,7 +447,7 @@ def event_itemfinished(self, event): file_details['time'] = event['time'] file_details['action'] = event['data']['action'] self.recent_files.append(file_details) - self.recent_files = self.recent_files[-5:] + self.recent_files = self.recent_files[-20:] self.state['update_files'] = True # end of the event processing dings From 2681c1e5841e2d6fae01c5538d703ec238ac189d Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 18:16:15 -0400 Subject: [PATCH 24/45] Edit update_files() Syncthing doesn't expose uploading files (yet), so remove that code Use symbols for downloaded and deleted files in Recently Synced menu Keep Syncing and Recently Synced menus visible when empty --- syncthing-ubuntu-indicator.py | 42 ++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 79f5503..71b8ecc 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -43,7 +43,6 @@ def __init__(self): self.create_menu() self.downloading_files = [] - self.uploading_files = [] self.recent_files = [] self.folders = [] self.devices = [] @@ -104,12 +103,20 @@ def create_menu(self): self.folder_menu_submenu = Gtk.Menu() self.folder_menu.set_submenu(self.folder_menu_submenu) - self.current_files_menu = Gtk.MenuItem('Current files') + sep = Gtk.SeparatorMenuItem() + sep.show() + self.menu.append(sep) + + self.current_files_menu = Gtk.MenuItem('Syncing') + self.current_files_menu.show() + self.current_files_menu.set_sensitive(False) self.menu.append(self.current_files_menu) self.current_files_submenu = Gtk.Menu() self.current_files_menu.set_submenu(self.current_files_submenu) self.recent_files_menu = Gtk.MenuItem('Recently synced') + self.recent_files_menu.show() + self.recent_files_menu.set_sensitive(False) self.menu.append(self.recent_files_menu) self.recent_files_submenu = Gtk.Menu() self.recent_files_menu.set_submenu(self.recent_files_submenu) @@ -556,44 +563,39 @@ def update_devices(self): self.state['update_devices'] = False def update_files(self): - self.current_files_menu.set_label(u'Syncing \u2191 %s \u2193 %s' % ( - len(self.uploading_files), len(self.downloading_files))) + self.current_files_menu.set_label(u'Syncing %s files' % ( + len(self.downloading_files))) - if (len(self.uploading_files), len(self.downloading_files)) == (0,0): - self.current_files_menu.hide() + if not self.downloading_files: + self.current_files_menu.set_sensitive(False) #self.set_state('idle') else: - # repopulate the current files menu + # Repopulate the current files menu + self.current_files_menu.set_sensitive(True) self.set_state('syncing') for child in self.current_files_submenu.get_children(): self.current_files_submenu.remove(child) - for f in self.uploading_files: - mi = Gtk.MenuItem(u'\u2191 [{}] {}'.format(f['folder'], f['file'])) - self.current_files_submenu.append(mi) - mi.show() for f in self.downloading_files: mi = Gtk.MenuItem(u'\u2193 [{}] {}'.format(f['folder'], f['file'])) self.current_files_submenu.append(mi) mi.show() self.current_files_menu.show() - # repopulate the recent files menu + # Repopulate the recent files menu if not self.recent_files: - self.recent_files_menu.hide() + self.recent_files_menu.set_sensitive(False) else: + self.recent_files_menu.set_sensitive(True) for child in self.recent_files_submenu.get_children(): self.recent_files_submenu.remove(child) for f in self.recent_files: - updown = u'\u2193' u'\u2191' if f['action'] == 'delete': - action = '(Del)' + icon = u'\u2612' # [x] else: - action = updown + icon = u'\u2193' # down arrow mi = Gtk.MenuItem( - u'{time} [{folder}] {action} {item}'.format( - action=action, - folder=f['folder'], - item=f['file'], + u'{icon} {time} [{folder}] {item}'.format( + icon=icon, folder=f['folder'], item=f['file'], time=self.convert_time(f['time']) ) ) From d3f0c6417fcc8d3c0f0131cd9d6a7c25693788ef Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 18:17:00 -0400 Subject: [PATCH 25/45] Update about box Use syncthing-ubuntu-indicator github website --- syncthing-ubuntu-indicator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 71b8ecc..2a2d5ac 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -694,8 +694,9 @@ def show_about(self, widget): dialog.set_logo(None) dialog.set_program_name('Syncthing Ubuntu Indicator') dialog.set_version(VERSION) - dialog.set_website('http://www.syncthing.net') - dialog.set_comments('This menu applet for systems supporting AppIndicator can show the status of a Syncthing instance') + dialog.set_website('https://github.com/icaruseffect/syncthing-ubuntu-indicator') + dialog.set_comments('This menu applet for systems supporting AppIndicator' + '\ncan show the status of a Syncthing instance') dialog.set_license(self.license()) dialog.run() dialog.destroy() From 7dca20b36792d41b31476f07907bafcd197a3372 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 9 May 2015 18:34:35 -0400 Subject: [PATCH 26/45] Keep count of consecutive connection errors Set default event interval to 10s Check uptime from /rest/system/status to see if Syncthing restarted --- syncthing-ubuntu-indicator.py | 48 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 23 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 2a2d5ac..f500d54 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -54,9 +54,8 @@ def __init__(self): self.syncthing_version = '' self.device_name = '' self.last_seen_id = 0 - self.rest_connected = False self.timeout_counter = 0 - self.ping_counter = 0 + self.count_connection_error = 0 self.session = FuturesSession() @@ -300,7 +299,7 @@ def rest_get(self, rest_path): f = self.session.get(self.syncthing_url(rest_path), params=params, headers=headers, - timeout=8) + timeout=9) f.add_done_callback(self.rest_receive_data) return False @@ -308,15 +307,18 @@ def rest_receive_data(self, future): try: r = future.result() except requests.exceptions.ConnectionError: - log.error("Couldn't connect to Syncthing REST interface at {}".format( + log.error( + "Couldn't connect to Syncthing REST interface at {}".format( self.syncthing_url(''))) - self.rest_connected = False - self.state['update_st_running'] = True - if self.ping_counter > 1: + self.count_connection_error += 1 + log.info('count_connection_error: %s' % self.count_connection_error) + if self.count_connection_error > 1: + self.state['update_st_running'] = True self.set_state('paused') return except (requests.exceptions.Timeout, socket.timeout): - log.warning('Timeout') + log.debug('Timeout') + GLib.idle_add(self.rest_get, '/rest/system/status') return except Exception as e: log.error('exception: {}'.format(e)) @@ -339,8 +341,10 @@ def rest_receive_data(self, future): self.set_state('error') return - self.set_state('idle') log.debug('rest_receive_data: {}'.format(rest_path)) + # Receiving data appears to have succeeded + self.count_connection_error = 0 + self.set_state('idle') # TODO: fix this if rest_path == '/rest/events': try: for qitem in json_data: @@ -469,6 +473,9 @@ def process_rest_system_connections(self, data): self.state['update_devices'] = True def process_rest_system_status(self, data): + if data['uptime'] < self.system_data.get('uptime', 0): + # Means that Syncthing restarted + self.last_seen_id = 0 self.system_data = data self.state['update_st_running'] = True @@ -488,7 +495,6 @@ def process_rest_system_ping(self, data): if data['ping'] == 'pong': log.info('Connected to Syncthing REST interface at {}'.format( self.syncthing_url(''))) - self.rest_connected = True self.ping_counter = 0 def process_rest_ping(self, data): @@ -525,9 +531,6 @@ def update_last_checked(self, isotime): def update_last_seen_id(self, lsi): if lsi > self.last_seen_id: self.last_seen_id = lsi - elif lsi < self.last_seen_id: - log.warning('received event id {} less than last_seen_id {}'.format( - lsi, self.last_seen_id)) def update_devices(self): self.devices_menu.set_label('Devices (%s connected)' % self.count_connected()) @@ -639,14 +642,14 @@ def update_folders(self): self.state['update_folders'] = False def update_st_running(self): - if self.rest_connected: + if self.count_connection_error <= 1: self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( self.syncthing_version, self.device_name)) self.mi_start_syncthing.set_sensitive(False) self.mi_restart_syncthing.set_sensitive(True) self.mi_shutdown_syncthing.set_sensitive(True) else: - self.title_menu.set_label('Syncthing: not running?') + self.title_menu.set_label('Could not connect to Syncthing') self.mi_start_syncthing.set_sensitive(True) self.mi_restart_syncthing.set_sensitive(False) self.mi_shutdown_syncthing.set_sensitive(False) @@ -708,7 +711,7 @@ def set_state(self, s=None): if (s == 'error') or self.errors: self.state['set_icon'] = 'error' - elif not self.rest_connected: + elif self.count_connection_error > 1: self.state['set_icon'] = 'paused' else: self.state['set_icon'] = self.folder_check_state() @@ -752,7 +755,7 @@ def leave(self, widget): def timeout_rest(self): self.timeout_counter = (self.timeout_counter + 1) % 10 - if self.rest_connected: + if self.count_connection_error == 0: GLib.idle_add(self.rest_get, '/rest/system/connections') GLib.idle_add(self.rest_get, '/rest/system/status') GLib.idle_add(self.rest_get, '/rest/system/error') @@ -760,14 +763,12 @@ def timeout_rest(self): GLib.idle_add(self.rest_get, '/rest/system/upgrade') GLib.idle_add(self.rest_get, '/rest/system/version') else: - self.ping_counter += 1 - log.debug('ping counter {}'.format(self.ping_counter)) - GLib.idle_add(self.rest_get, '/rest/system/ping') + GLib.idle_add(self.rest_get, '/rest/system/status') return True def timeout_events(self): - if self.rest_connected: + if self.count_connection_error == 0: GLib.idle_add(self.rest_get, '/rest/events') return True @@ -778,8 +779,9 @@ def timeout_events(self): signal.signal(signal.SIGINT, signal.SIG_DFL) parser = argparse.ArgumentParser() - parser.add_argument('--loglevel', choices=['debug', 'info', 'warning', 'error'], default='info') - parser.add_argument('--timeout-event', type=int, default=5) + parser.add_argument('--loglevel', + choices=['debug', 'info', 'warning', 'error'], default='info') + parser.add_argument('--timeout-event', type=int, default=10) parser.add_argument('--timeout-rest', type=int, default=30) parser.add_argument('--timeout-gui', type=int, default=5) From 29aea53eee2c5fef34c4bd70f8820650daece3aa Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sun, 10 May 2015 08:40:06 -0400 Subject: [PATCH 27/45] Abbreviate long pathnames in files menus --- syncthing-ubuntu-indicator.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index f500d54..5e6f393 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -22,6 +22,19 @@ VERSION = 'v0.3.0' +def shorten_path(text, maxlength=80): + if len(text) <= maxlength: + return text + head, tail = os.path.split(text) + if len(tail) > maxlength: + return tail[:maxlength] # TODO: separate file extension + while len(head) + len(tail) > maxlength: + head = '/'.join(head.split('/')[:-1]) + if head == '': + return '.../' + tail + return head + '/.../' + tail + + class Main(object): def __init__(self): log.info('Started main procedure') @@ -579,7 +592,9 @@ def update_files(self): for child in self.current_files_submenu.get_children(): self.current_files_submenu.remove(child) for f in self.downloading_files: - mi = Gtk.MenuItem(u'\u2193 [{}] {}'.format(f['folder'], f['file'])) + mi = Gtk.MenuItem(u'\u2193 [{}] {}'.format( + f['folder'], + shorten_path(f['file']))) self.current_files_submenu.append(mi) mi.show() self.current_files_menu.show() @@ -598,7 +613,9 @@ def update_files(self): icon = u'\u2193' # down arrow mi = Gtk.MenuItem( u'{icon} {time} [{folder}] {item}'.format( - icon=icon, folder=f['folder'], item=f['file'], + icon=icon, + folder=f['folder'], + item=shorten_path(f['file']), time=self.convert_time(f['time']) ) ) From daac5bc59e672bac0b360ad98589355e53e7b083 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sun, 10 May 2015 08:41:57 -0400 Subject: [PATCH 28/45] Show most recently synced files first Also track type of items in progress --- syncthing-ubuntu-indicator.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 5e6f393..b4a76b3 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -448,6 +448,7 @@ def event_itemstarted(self, event): log.debug(u'item started: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], 'file': event['data']['item'], + 'type': event['data']['type'], 'direction': 'down'} self.downloading_files.append(file_details) for elm in self.folders: @@ -461,6 +462,7 @@ def event_itemfinished(self, event): log.debug(u'item finished: {}'.format(event['data']['item'])) file_details = {'folder': event['data']['folder'], 'file': event['data']['item'], + 'type': event['data']['type'], 'direction': 'down'} try: self.downloading_files.remove(file_details) @@ -470,8 +472,8 @@ def event_itemfinished(self, event): event['data']['item'])) file_details['time'] = event['time'] file_details['action'] = event['data']['action'] - self.recent_files.append(file_details) - self.recent_files = self.recent_files[-20:] + self.recent_files.insert(0, file_details) + self.recent_files = self.recent_files[:20] self.state['update_files'] = True # end of the event processing dings From e1ef1630a1b1a05202367cbeb3deaf882565a3c2 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sun, 10 May 2015 08:43:16 -0400 Subject: [PATCH 29/45] Edit update_devices() --- syncthing-ubuntu-indicator.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index b4a76b3..2d22fc2 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -548,23 +548,23 @@ def update_last_seen_id(self, lsi): self.last_seen_id = lsi def update_devices(self): - self.devices_menu.set_label('Devices (%s connected)' % self.count_connected()) - if len(self.devices) == 0: - self.devices_menu.set_label('Devices (0 connected)') + if not self.devices: + self.devices_menu.set_label('No devices)') self.devices_menu.set_sensitive(False) else: + self.devices_menu.set_label('Devices ({}/{})'.format( + self.count_connected(), len(self.devices) - 1)) self.devices_menu.set_sensitive(True) if len(self.devices) == len(self.devices_submenu) + 1: - # this updates the devices menu + # Update the devices menu for mi in self.devices_submenu: for elm in self.devices: if mi.get_label() == elm['name']: mi.set_label(elm['name']) mi.set_sensitive(elm['state'] == 'connected') - else: - # this populates the devices menu with devices from config + # Populate the devices menu with devices from config for child in self.devices_submenu.get_children(): self.devices_submenu.remove(child) From 586779d179c8e68577560e8da776752da6286ab9 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sun, 10 May 2015 08:47:11 -0400 Subject: [PATCH 30/45] Get folders and devices from /rest/system/config This allows us to track added/removed devices and folders if Syncthing restarts --- syncthing-ubuntu-indicator.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 2d22fc2..d97410b 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -409,6 +409,8 @@ def event_startupcomplete(self, event): self.set_state('idle') time = self.convert_time(event['time']) log.debug('Startup done at %s' % time) + # Check config for added/removed devices or folders. + GLib.idle_add(self.rest_get, '/rest/system/config') def event_ping(self, event): self.last_ping = dateutil.parser.parse(event['time']) @@ -487,6 +489,29 @@ def process_rest_system_connections(self, data): nid['state'] = 'connected' self.state['update_devices'] = True + def process_rest_system_config(self, data): + log.info('Processing /rest/system/config') + self.api_key = data['gui']['apiKey'] + + newfolders = [] + for elem in data['folders']: + newfolders.append({ + 'id': elem['id'], + 'path': elem['path'], + 'state': 'unknown' + }) + + newdevices = [] + for elem in data['devices']: + newdevices.append({ + 'id': elem['deviceID'], + 'name': elem['name'], + 'state': 'disconnected' + }) + + self.folders = newfolders + self.devices = newdevices + def process_rest_system_status(self, data): if data['uptime'] < self.system_data.get('uptime', 0): # Means that Syncthing restarted From e7b0dfc19fc43bcba48a51cf3de6624436a83bc0 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:10:33 -0400 Subject: [PATCH 31/45] Add option to hide start, restart, shutdown menus --no-shutdown --- syncthing-ubuntu-indicator.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index d97410b..7ddce62 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -36,8 +36,9 @@ def shorten_path(text, maxlength=80): class Main(object): - def __init__(self): + def __init__(self, args): log.info('Started main procedure') + self.args=args self.wd = os.path.normpath(os.path.abspath(os.path.split(__file__)[0])) self.icon_path = os.path.join(self.wd, 'icons') self.ind = appindicator.Indicator.new_with_path( @@ -152,25 +153,27 @@ def create_menu(self): self.mi_start_syncthing = Gtk.MenuItem('Start Syncthing') self.mi_start_syncthing.connect('activate', self.syncthing_start) self.mi_start_syncthing.set_sensitive(False) - self.mi_start_syncthing.show() self.more_submenu.append(self.mi_start_syncthing) self.mi_restart_syncthing = Gtk.MenuItem('Restart Syncthing') self.mi_restart_syncthing.connect('activate', self.syncthing_restart) self.mi_restart_syncthing.set_sensitive(False) - self.mi_restart_syncthing.show() self.more_submenu.append(self.mi_restart_syncthing) self.mi_shutdown_syncthing = Gtk.MenuItem('Shutdown Syncthing') self.mi_shutdown_syncthing.connect('activate', self.syncthing_shutdown) self.mi_shutdown_syncthing.set_sensitive(False) - self.mi_shutdown_syncthing.show() self.more_submenu.append(self.mi_shutdown_syncthing) sep = Gtk.SeparatorMenuItem() - sep.show() self.more_submenu.append(sep) + if not self.args.no_shutdown: + self.mi_start_syncthing.show() + self.mi_restart_syncthing.show() + self.mi_shutdown_syncthing.show() + sep.show() + self.about_menu = Gtk.MenuItem('About Indicator') self.about_menu.connect('activate', self.show_about) self.about_menu.show() @@ -825,9 +828,14 @@ def timeout_events(self): parser = argparse.ArgumentParser() parser.add_argument('--loglevel', choices=['debug', 'info', 'warning', 'error'], default='info') - parser.add_argument('--timeout-event', type=int, default=10) - parser.add_argument('--timeout-rest', type=int, default=30) - parser.add_argument('--timeout-gui', type=int, default=5) + parser.add_argument('--timeout-event', type=int, default=10, metavar='N', + help='Interval for polling event interface, in seconds. Default: %(default)s') + parser.add_argument('--timeout-rest', type=int, default=30, metavar='N', + help='Interval for polling REST interface, in seconds. Default: %(default)s') + parser.add_argument('--timeout-gui', type=int, default=5, metavar='N', + help='Interval for refreshing GUI, in seconds. Default: %(default)s') + parser.add_argument('--no-shutdown', action='store_true', + help='Hide Start, Restart, and Shutdown Syncthing menus') args = parser.parse_args() for arg in [args.timeout_event, args.timeout_rest, args.timeout_gui]: @@ -846,5 +854,5 @@ def timeout_events(self): requests_log.setLevel(log.WARNING) requests_log.propagate = True - app = Main() + app = Main(args) Gtk.main() From f7ee3dbc0cf8a0aede829b4e52574d120467f028 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:16:24 -0400 Subject: [PATCH 32/45] Cleanup Say 'download' instead of 'sync', because currently we can't track uploads --- syncthing-ubuntu-indicator.py | 40 +++++++++++++++++------------------ 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 7ddce62..9c6d2b0 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -90,7 +90,7 @@ def create_menu(self): self.title_menu.set_sensitive(False) self.menu.append(self.title_menu) - self.syncthing_upgrade_menu = Gtk.MenuItem('Update check') + self.syncthing_upgrade_menu = Gtk.MenuItem('Upgrade check') self.syncthing_upgrade_menu.connect('activate', self.open_releases_page) self.menu.append(self.syncthing_upgrade_menu) @@ -120,14 +120,14 @@ def create_menu(self): sep.show() self.menu.append(sep) - self.current_files_menu = Gtk.MenuItem('Syncing') + self.current_files_menu = Gtk.MenuItem('Downloading files') self.current_files_menu.show() self.current_files_menu.set_sensitive(False) self.menu.append(self.current_files_menu) self.current_files_submenu = Gtk.Menu() self.current_files_menu.set_submenu(self.current_files_submenu) - self.recent_files_menu = Gtk.MenuItem('Recently synced') + self.recent_files_menu = Gtk.MenuItem('Recently downloaded') self.recent_files_menu.show() self.recent_files_menu.set_sensitive(False) self.menu.append(self.recent_files_menu) @@ -188,7 +188,7 @@ def create_menu(self): def load_config_begin(self): - ''' read needed values from config file ''' + ''' Read needed values from config file ''' confdir = GLib.get_user_config_dir() if not confdir: confdir = os.path.expanduser('~/.config') @@ -273,7 +273,6 @@ def load_config_finish(self, fp, async_result): self.leave() # Start processes - GLib.idle_add(self.rest_get, '/rest/system/ping') GLib.idle_add(self.rest_get, '/rest/system/version') GLib.idle_add(self.rest_get, '/rest/system/connections') GLib.idle_add(self.rest_get, '/rest/system/status') @@ -286,14 +285,12 @@ def load_config_finish(self, fp, async_result): def syncthing_url(self, url): - ''' creates a url from given values and the address read from file ''' + ''' Creates a url from given values and the address read from file ''' return urlparse.urljoin(self.syncthing_base, url) - def open_web_ui(self, *args): webbrowser.open(self.syncthing_url('')) - def open_releases_page(self, *args): webbrowser.open('https://github.com/syncthing/syncthing/releases') @@ -306,12 +303,13 @@ def rest_post(self, rest_path): return False def rest_get(self, rest_path): - log.debug('rest_get {}'.format(rest_path)) - # url for the included testserver: http://localhost:5115 - headers = {'X-API-Key': self.api_key} - params = {} + params = '' if rest_path == '/rest/events': params = {'since': self.last_seen_id} + + log.info('rest_get {} {}'.format(rest_path, params)) + # url for the included testserver: http://localhost:5115 + headers = {'X-API-Key': self.api_key} f = self.session.get(self.syncthing_url(rest_path), params=params, headers=headers, @@ -341,6 +339,7 @@ def rest_receive_data(self, future): return rest_path = urlparse.urlparse(r.url).path + rest_query = urlparse.urlparse(r.url).query if r.status_code != 200: log.warning('rest_receive_data: {0} failed ({1})'.format( rest_path, r.status_code)) @@ -357,10 +356,10 @@ def rest_receive_data(self, future): self.set_state('error') return - log.debug('rest_receive_data: {}'.format(rest_path)) # Receiving data appears to have succeeded self.count_connection_error = 0 self.set_state('idle') # TODO: fix this + log.debug('rest_receive_data: {} {}'.format(rest_path, rest_query)) if rest_path == '/rest/events': try: for qitem in json_data: @@ -380,9 +379,12 @@ def rest_receive_data(self, future): def process_event(self, event): t = event.get('type').lower() if hasattr(self, 'event_{}'.format(t)): - log.debug('received event: {}'.format(event.get('type'))) + log.debug('received event: {} {}'.format( + event.get('id'), event.get('type'))) + pass else: - log.debug('ignoring unknown event: {}'.format(event.get('type'))) + log.debug('ignoring event: {} {}'.format( + event.get('id'), event.get('type'))) #log.debug(json.dumps(event, indent=4)) fn = getattr(self, 'event_{}'.format(t), self.event_unknown_event)(event) @@ -395,7 +397,7 @@ def event_statechanged(self, event): for elem in self.folders: if elem['id'] == event['data']['folder']: elem['state'] = event['data']['to'] - self.state['update_folders'] = True + self.state['update_folders'] = True self.set_state() def event_foldersummary(self, event): @@ -501,7 +503,7 @@ def process_rest_system_config(self, data): newfolders.append({ 'id': elem['id'], 'path': elem['path'], - 'state': 'unknown' + 'state': 'unknown', }) newdevices = [] @@ -538,7 +540,6 @@ def process_rest_system_ping(self, data): if data['ping'] == 'pong': log.info('Connected to Syncthing REST interface at {}'.format( self.syncthing_url(''))) - self.ping_counter = 0 def process_rest_ping(self, data): if data['ping'] == 'pong': @@ -813,14 +814,12 @@ def timeout_rest(self): GLib.idle_add(self.rest_get, '/rest/system/status') return True - def timeout_events(self): if self.count_connection_error == 0: GLib.idle_add(self.rest_get, '/rest/events') return True - if __name__ == '__main__': import signal signal.signal(signal.SIGINT, signal.SIG_DFL) @@ -845,7 +844,6 @@ def timeout_events(self): TIMEOUT_REST = args.timeout_rest TIMEOUT_GUI = args.timeout_gui - # Setup logging: loglevels = {'debug': log.DEBUG, 'info': log.INFO, 'warning': log.WARNING, 'error': log.ERROR} log.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', From fc7e778dad1e47140916f2ce15ebfc7b409af83a Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:21:15 -0400 Subject: [PATCH 33/45] Separate device 'connected' and 'state' Process FolderCompletion event --- syncthing-ubuntu-indicator.py | 79 ++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 33 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 9c6d2b0..a7ccc22 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -252,7 +252,8 @@ def load_config_finish(self, fp, async_result): 'id': elem.getAttribute('id'), 'name': elem.getAttribute('name'), 'compression': elem.getAttribute('compression'), - 'state': 'disconnected', + 'state': '', + 'connected': False }) except: log.error('config has no devices configured') @@ -406,6 +407,15 @@ def event_foldersummary(self, event): elem.update(event['data']['summary']) self.state['update_folders'] = True + def event_foldercompletion(self, event): + for dev in self.devices: + if dev['id'] == event['data']['device']: + if event['data']['completion'] < 100: + dev['state'] = 'syncing' + else: + dev['state'] = '' + self.state['update_devices'] = True + def event_starting(self, event): self.set_state('paused') log.info('Received that Syncthing was starting at %s' % event['time']) @@ -438,17 +448,17 @@ def event_devicediscovered(self, event): self.state['update_devices'] = True def event_deviceconnected(self, event): - for elem in self.devices: - if event['data']['id'] == elem['id']: - elem['state'] = 'connected' - log.info('Device connected: %s' % elem['name']) + for dev in self.devices: + if event['data']['id'] == dev['id']: + dev['connected'] = True + log.info('Device connected: %s' % dev['name']) self.state['update_devices'] = True def event_devicedisconnected(self, event): - for elem in self.devices: - if event['data']['id'] == elem['id']: - elem['state'] = 'disconnected' - log.info('Device disconnected: %s' % elem['name']) + for dev in self.devices: + if event['data']['id'] == dev['id']: + dev['connected'] = False + log.info('Device disconnected: %s' % dev['name']) self.state['update_devices'] = True def event_itemstarted(self, event): @@ -488,11 +498,11 @@ def event_itemfinished(self, event): # begin REST processing functions def process_rest_system_connections(self, data): - for elem in data['connections'].iterkeys(): - for nid in self.devices: - if nid['id'] == elem: - nid['state'] = 'connected' - self.state['update_devices'] = True + for elem in data['connections']: + for dev in self.devices: + if dev['id'] == elem: + dev['connected'] = True + self.state['update_devices'] = True def process_rest_system_config(self, data): log.info('Processing /rest/system/config') @@ -511,7 +521,8 @@ def process_rest_system_config(self, data): newdevices.append({ 'id': elem['deviceID'], 'name': elem['name'], - 'state': 'disconnected' + 'state': '', + 'connected': False, }) self.folders = newfolders @@ -577,36 +588,36 @@ def update_last_seen_id(self, lsi): self.last_seen_id = lsi def update_devices(self): - if not self.devices: - self.devices_menu.set_label('No devices)') - self.devices_menu.set_sensitive(False) - else: + if self.devices: + # TODO: set icon if zero devices are connected self.devices_menu.set_label('Devices ({}/{})'.format( self.count_connected(), len(self.devices) - 1)) self.devices_menu.set_sensitive(True) - if len(self.devices) == len(self.devices_submenu) + 1: + if len(self.devices_submenu) == len(self.devices) - 1: # Update the devices menu for mi in self.devices_submenu: for elm in self.devices: - if mi.get_label() == elm['name']: + if mi.get_label().split(' ')[0] == elm['name']: mi.set_label(elm['name']) - mi.set_sensitive(elm['state'] == 'connected') + mi.set_sensitive(elm['connected']) else: - # Populate the devices menu with devices from config + # Repopulate the devices menu for child in self.devices_submenu.get_children(): self.devices_submenu.remove(child) - for nid in sorted(self.devices, key=lambda nid: nid['name']): - if nid['id'] == self.system_data.get('myID', None): - self.device_name = nid['name'] + for elm in sorted(self.devices, key=lambda elm: elm['name']): + if elm['id'] == self.system_data.get('myID', None): + self.device_name = elm['name'] self.state['update_st_running'] = True - continue - - mi = Gtk.MenuItem(nid['name']) - mi.set_sensitive(nid['state'] == 'connected') - self.devices_submenu.append(mi) - mi.show() + else: + mi = Gtk.MenuItem(elm['name']) + mi.set_sensitive(elm['connected']) + self.devices_submenu.append(mi) + mi.show() + else: + self.devices_menu.set_label('No devices)') + self.devices_menu.set_sensitive(False) self.state['update_devices'] = False def update_files(self): @@ -698,13 +709,15 @@ def update_st_running(self): self.mi_shutdown_syncthing.set_sensitive(True) else: self.title_menu.set_label('Could not connect to Syncthing') + for dev in self.devices: + dev['connected'] = False self.mi_start_syncthing.set_sensitive(True) self.mi_restart_syncthing.set_sensitive(False) self.mi_shutdown_syncthing.set_sensitive(False) def count_connected(self): - return len([e for e in self.devices if e['state'] == 'connected']) + return len([e for e in self.devices if e['connected']]) def syncthing_start(self, *args): From b2bc37e666a9efa7f9bdb599c3e6bbaacf8225a4 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:36:58 -0400 Subject: [PATCH 34/45] Cleanup --- syncthing-ubuntu-indicator.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index a7ccc22..1e57f88 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -186,7 +186,6 @@ def create_menu(self): self.ind.set_menu(self.menu) - def load_config_begin(self): ''' Read needed values from config file ''' confdir = GLib.get_user_config_dir() @@ -284,7 +283,6 @@ def load_config_finish(self, fp, async_result): GLib.timeout_add_seconds(TIMEOUT_REST, self.timeout_rest) GLib.timeout_add_seconds(TIMEOUT_EVENT, self.timeout_events) - def syncthing_url(self, url): ''' Creates a url from given values and the address read from file ''' return urlparse.urljoin(self.syncthing_base, url) @@ -295,7 +293,6 @@ def open_web_ui(self, *args): def open_releases_page(self, *args): webbrowser.open('https://github.com/syncthing/syncthing/releases') - def rest_post(self, rest_path): log.debug('rest_post {}'.format(rest_path)) headers = {'X-API-Key': self.api_key} @@ -375,7 +372,6 @@ def rest_receive_data(self, future): 'process_{}'.format(rest_path.strip('/').replace('/','_')) )(json_data) - # processing of the events coming from the event interface def process_event(self, event): t = event.get('type').lower() @@ -422,14 +418,13 @@ def event_starting(self, event): def event_startupcomplete(self, event): self.set_state('idle') - time = self.convert_time(event['time']) - log.debug('Startup done at %s' % time) + log.info('Syncthing startup complete at %s' % + self.convert_time(event['time'])) # Check config for added/removed devices or folders. GLib.idle_add(self.rest_get, '/rest/system/config') def event_ping(self, event): self.last_ping = dateutil.parser.parse(event['time']) - log.debug('A ping was sent at %s' % self.last_ping.strftime('%H:%M')) def event_devicediscovered(self, event): found = False @@ -567,10 +562,8 @@ def process_rest_system_error(self, data): self.set_state('error') else: self.mi_errors.hide() - # end of the REST processing functions - def update(self): for func in self.state: if self.state[func]: @@ -715,11 +708,9 @@ def update_st_running(self): self.mi_restart_syncthing.set_sensitive(False) self.mi_shutdown_syncthing.set_sensitive(False) - def count_connected(self): return len([e for e in self.devices if e['connected']]) - def syncthing_start(self, *args): cmd = os.path.join(self.wd, 'start-syncthing.sh') log.info('Starting {}'.format(cmd)) @@ -729,16 +720,12 @@ def syncthing_start(self, *args): log.error("Couldn't run {}: {}".format(cmd, e)) return self.state['update_st_running'] = True - self.last_seen_id = 0 def syncthing_restart(self, *args): self.rest_post('/rest/system/restart') - self.last_seen_id = 0 def syncthing_shutdown(self, *args): self.rest_post('/rest/system/shutdown') - self.last_seen_id = 0 - def convert_time(self, time): return dateutil.parser.parse(time).strftime('%x %X') @@ -746,7 +733,6 @@ def convert_time(self, time): def calc_speed(self, old, new): return old / (new * 10) - def license(self): with open(os.path.join(self.wd, 'LICENSE'), 'r') as f: lic = f.read() @@ -765,7 +751,6 @@ def show_about(self, widget): dialog.run() dialog.destroy() - def set_state(self, s=None): if not s: s = self.state['set_icon'] @@ -777,7 +762,6 @@ def set_state(self, s=None): else: self.state['set_icon'] = self.folder_check_state() - def folder_check_state(self): state = {'syncing': 0, 'idle': 0, 'cleaning': 0, 'scanning': 0, 'unknown': 0} @@ -792,7 +776,6 @@ def folder_check_state(self): else: return 'idle' - def set_icon(self): icon = { 'updating': {'name': 'syncthing-client-updating', 'descr': 'Updating'}, @@ -807,13 +790,10 @@ def set_icon(self): self.ind.set_attention_icon(icon[self.state['set_icon']]['name']) self.ind.set_icon_full(icon[self.state['set_icon']]['name'], icon[self.state['set_icon']]['descr']) - #GLib.timeout_add_seconds(1, self.set_icon) - def leave(self, widget): Gtk.main_quit() - def timeout_rest(self): self.timeout_counter = (self.timeout_counter + 1) % 10 if self.count_connection_error == 0: From c23758e2526beb2b7f734725305266f56e6cfbad Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:39:41 -0400 Subject: [PATCH 35/45] Don't show errors if upgrading is disabled in build --- syncthing-ubuntu-indicator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 1e57f88..8674442 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -341,7 +341,11 @@ def rest_receive_data(self, future): if r.status_code != 200: log.warning('rest_receive_data: {0} failed ({1})'.format( rest_path, r.status_code)) - self.set_state('error') + if r.url == '/rest/system/upgrade': + # Debian/Ubuntu packages of Syncthing have this disabled + pass + else: + self.set_state('error') if rest_path == '/rest/system/ping': # Basic version check: try the old REST path GLib.idle_add(self.rest_get, '/rest/ping') From e6eac8613d33df544df14833525471be02ce042a Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:40:06 -0400 Subject: [PATCH 36/45] Move config check to event_starting --- syncthing-ubuntu-indicator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 8674442..e95803c 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -419,13 +419,14 @@ def event_foldercompletion(self, event): def event_starting(self, event): self.set_state('paused') log.info('Received that Syncthing was starting at %s' % event['time']) + # Check for added/removed devices or folders. + GLib.idle_add(self.rest_get, '/rest/system/config') + GLib.idle_add(self.rest_get, '/rest/system/version') def event_startupcomplete(self, event): self.set_state('idle') log.info('Syncthing startup complete at %s' % self.convert_time(event['time'])) - # Check config for added/removed devices or folders. - GLib.idle_add(self.rest_get, '/rest/system/config') def event_ping(self, event): self.last_ping = dateutil.parser.parse(event['time']) From 3e6cec825c22fd09ed284c29038385980e90ca7b Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:40:26 -0400 Subject: [PATCH 37/45] Avoid listing same file multiple times --- syncthing-ubuntu-indicator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index e95803c..154e1ef 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -467,7 +467,8 @@ def event_itemstarted(self, event): 'file': event['data']['item'], 'type': event['data']['type'], 'direction': 'down'} - self.downloading_files.append(file_details) + if file_details not in self.downloading_files: + self.downloading_files.append(file_details) for elm in self.folders: if elm['id'] == event['data']['folder']: elm['state'] = 'syncing' From 9a2a4b5efa181ee8485a5aaba32c98c8f123b8d6 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:40:57 -0400 Subject: [PATCH 38/45] Icons in downloading menu --- syncthing-ubuntu-indicator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 154e1ef..0f60a02 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -620,7 +620,7 @@ def update_devices(self): self.state['update_devices'] = False def update_files(self): - self.current_files_menu.set_label(u'Syncing %s files' % ( + self.current_files_menu.set_label(u'Downloading %s files' % ( len(self.downloading_files))) if not self.downloading_files: @@ -647,14 +647,15 @@ def update_files(self): self.recent_files_menu.set_sensitive(True) for child in self.recent_files_submenu.get_children(): self.recent_files_submenu.remove(child) + icons = {'delete': u'\u2612', # [x] + 'update': u'\u2193', # down arrow + 'dir': u'\U0001f4c1', # folder + 'file': u'\U0001f4c4', # file + } for f in self.recent_files: - if f['action'] == 'delete': - icon = u'\u2612' # [x] - else: - icon = u'\u2193' # down arrow mi = Gtk.MenuItem( u'{icon} {time} [{folder}] {item}'.format( - icon=icon, + icon=icons.get(f['action'], 'unknown'), folder=f['folder'], item=shorten_path(f['file']), time=self.convert_time(f['time']) From 6dc39372ddfc90fb90f58d98e2b82a3216394fee Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:41:08 -0400 Subject: [PATCH 39/45] Don't show empty strings --- syncthing-ubuntu-indicator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 0f60a02..bc7ea88 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -702,8 +702,9 @@ def update_folders(self): def update_st_running(self): if self.count_connection_error <= 1: - self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( - self.syncthing_version, self.device_name)) + if self.syncthing_version and self.device_name: + self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( + self.syncthing_version, self.device_name)) self.mi_start_syncthing.set_sensitive(False) self.mi_restart_syncthing.set_sensitive(True) self.mi_shutdown_syncthing.set_sensitive(True) From 4d3e821fac27f295c446955cbcea35f564420b78 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Tue, 12 May 2015 21:41:58 -0400 Subject: [PATCH 40/45] rest_system_upgrade --- syncthing-ubuntu-indicator.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index bc7ea88..ebbda72 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -330,6 +330,7 @@ def rest_receive_data(self, future): return except (requests.exceptions.Timeout, socket.timeout): log.debug('Timeout') + # Timeout may be because Syncthing restarted and event ID reset. GLib.idle_add(self.rest_get, '/rest/system/status') return except Exception as e: @@ -493,11 +494,9 @@ def event_itemfinished(self, event): self.recent_files.insert(0, file_details) self.recent_files = self.recent_files[:20] self.state['update_files'] = True - # end of the event processing dings # begin REST processing functions - def process_rest_system_connections(self, data): for elem in data['connections']: for dev in self.devices: @@ -537,12 +536,14 @@ def process_rest_system_status(self, data): self.state['update_st_running'] = True def process_rest_system_upgrade(self, data): + self.syncthing_version = data['running'] if data['newer']: self.syncthing_upgrade_menu.set_label( - 'New version available: {}'.format(data['latest'])) + 'New version available: %s' % data['latest']) self.syncthing_upgrade_menu.show() else: self.syncthing_upgrade_menu.hide() + self.state['update_st_running'] = True def process_rest_system_version(self, data): self.syncthing_version = data['version'] @@ -727,13 +728,17 @@ def syncthing_start(self, *args): except Exception as e: log.error("Couldn't run {}: {}".format(cmd, e)) return + GLib.idle_add(self.rest_get, '/rest/system/status') + GLib.idle_add(self.rest_get, '/rest/system/version') self.state['update_st_running'] = True def syncthing_restart(self, *args): self.rest_post('/rest/system/restart') + GLib.idle_add(self.rest_get, '/rest/system/status') def syncthing_shutdown(self, *args): self.rest_post('/rest/system/shutdown') + GLib.idle_add(self.rest_get, '/rest/system/status') def convert_time(self, time): return dateutil.parser.parse(time).strftime('%x %X') From 40b3421a5673da61e6b9217ba7050f5ac8d42eea Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Thu, 14 May 2015 00:18:11 -0400 Subject: [PATCH 41/45] Cleanup --- syncthing-ubuntu-indicator.py | 53 +++++++++++++++-------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index ebbda72..111587a 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -22,6 +22,7 @@ VERSION = 'v0.3.0' + def shorten_path(text, maxlength=80): if len(text) <= maxlength: return text @@ -38,7 +39,7 @@ def shorten_path(text, maxlength=80): class Main(object): def __init__(self, args): log.info('Started main procedure') - self.args=args + self.args = args self.wd = os.path.normpath(os.path.abspath(os.path.split(__file__)[0])) self.icon_path = os.path.join(self.wd, 'icons') self.ind = appindicator.Indicator.new_with_path( @@ -70,21 +71,13 @@ def __init__(self, args): self.last_seen_id = 0 self.timeout_counter = 0 self.count_connection_error = 0 - self.session = FuturesSession() GLib.idle_add(self.load_config_begin) - def create_menu(self): self.menu = Gtk.Menu() - #self.last_checked_menu = Gtk.MenuItem('Last checked: ?') - #self.last_checked_menu.show() - #self.last_checked_menu.set_sensitive(False) - #self.menu.append(self.last_checked_menu) - #self.update_last_checked(datetime.datetime.now(pytz.utc).isoformat()) - self.title_menu = Gtk.MenuItem('Syncthing') self.title_menu.show() self.title_menu.set_sensitive(False) @@ -127,7 +120,7 @@ def create_menu(self): self.current_files_submenu = Gtk.Menu() self.current_files_menu.set_submenu(self.current_files_submenu) - self.recent_files_menu = Gtk.MenuItem('Recently downloaded') + self.recent_files_menu = Gtk.MenuItem('Recently updated') self.recent_files_menu.show() self.recent_files_menu.set_sensitive(False) self.menu.append(self.recent_files_menu) @@ -279,9 +272,9 @@ def load_config_finish(self, fp, async_result): GLib.idle_add(self.rest_get, '/rest/system/upgrade') GLib.idle_add(self.rest_get, '/rest/system/error') GLib.idle_add(self.rest_get, '/rest/events') - GLib.timeout_add_seconds(TIMEOUT_GUI, self.update) - GLib.timeout_add_seconds(TIMEOUT_REST, self.timeout_rest) - GLib.timeout_add_seconds(TIMEOUT_EVENT, self.timeout_events) + GLib.timeout_add_seconds(self.args.timeout_gui, self.update) + GLib.timeout_add_seconds(self.args.timeout_rest, self.timeout_rest) + GLib.timeout_add_seconds(self.args.timeout_event, self.timeout_events) def syncthing_url(self, url): ''' Creates a url from given values and the address read from file ''' @@ -321,9 +314,9 @@ def rest_receive_data(self, future): except requests.exceptions.ConnectionError: log.error( "Couldn't connect to Syncthing REST interface at {}".format( - self.syncthing_url(''))) + self.syncthing_base)) self.count_connection_error += 1 - log.info('count_connection_error: %s' % self.count_connection_error) + log.info('count_connection_error: {}'.format(self.count_connection_error)) if self.count_connection_error > 1: self.state['update_st_running'] = True self.set_state('paused') @@ -342,8 +335,8 @@ def rest_receive_data(self, future): if r.status_code != 200: log.warning('rest_receive_data: {0} failed ({1})'.format( rest_path, r.status_code)) - if r.url == '/rest/system/upgrade': - # Debian/Ubuntu packages of Syncthing have this disabled + if rest_path == '/rest/system/upgrade': + # Debian/Ubuntu Syncthing packages disable upgrade check pass else: self.set_state('error') @@ -361,21 +354,22 @@ def rest_receive_data(self, future): # Receiving data appears to have succeeded self.count_connection_error = 0 - self.set_state('idle') # TODO: fix this + self.set_state('idle') # TODO: fix this log.debug('rest_receive_data: {} {}'.format(rest_path, rest_query)) if rest_path == '/rest/events': try: for qitem in json_data: self.process_event(qitem) except Exception as e: - log.warning('rest_receive_data: error processing event ({})'.format(e)) + log.warning( + 'rest_receive_data: error processing event ({})'.format(e)) log.debug(qitem) self.set_state('error') else: fn = getattr( - self, - 'process_{}'.format(rest_path.strip('/').replace('/','_')) - )(json_data) + self, + 'process_{}'.format(rest_path.strip('/').replace('/', '_')) + )(json_data) # processing of the events coming from the event interface def process_event(self, event): @@ -438,7 +432,7 @@ def event_devicediscovered(self, event): if elm['id'] == event['data']['device']: elm['state'] = 'discovered' found = True - if found == False: + if not found: log.warn('unknown device discovered') self.devices.append({ 'id': event['data']['device'], @@ -648,10 +642,10 @@ def update_files(self): self.recent_files_menu.set_sensitive(True) for child in self.recent_files_submenu.get_children(): self.recent_files_submenu.remove(child) - icons = {'delete': u'\u2612', # [x] - 'update': u'\u2193', # down arrow - 'dir': u'\U0001f4c1', # folder - 'file': u'\U0001f4c4', # file + icons = {'delete': u'\u2612', # [x] + 'update': u'\u2193', # down arrow + 'dir': u'\U0001f4c1', # folder + 'file': u'\U0001f4c4', # file } for f in self.recent_files: mi = Gtk.MenuItem( @@ -706,6 +700,8 @@ def update_st_running(self): if self.syncthing_version and self.device_name: self.title_menu.set_label(u'Syncthing {0} \u2022 {1}'.format( self.syncthing_version, self.device_name)) + else: + self.title_menu.set_label(u'Syncthing') self.mi_start_syncthing.set_sensitive(False) self.mi_restart_syncthing.set_sensitive(True) self.mi_shutdown_syncthing.set_sensitive(True) @@ -846,9 +842,6 @@ def timeout_events(self): for arg in [args.timeout_event, args.timeout_rest, args.timeout_gui]: if arg < 1: sys.exit('Timeouts must be integers greater than 0') - TIMEOUT_EVENT = args.timeout_event - TIMEOUT_REST = args.timeout_rest - TIMEOUT_GUI = args.timeout_gui loglevels = {'debug': log.DEBUG, 'info': log.INFO, 'warning': log.WARNING, 'error': log.ERROR} From dbbca14eed8ea84233abe7b8c17a2a247340617b Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 13 Jun 2015 10:27:58 -0400 Subject: [PATCH 42/45] Cleanup load_config functions --- syncthing-ubuntu-indicator.py | 95 ++++++++++++++--------------------- 1 file changed, 39 insertions(+), 56 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 111587a..8b5690d 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -186,7 +186,8 @@ def load_config_begin(self): confdir = os.path.expanduser('~/.config') conffile = os.path.join(confdir, 'syncthing', 'config.xml') if not os.path.isfile(conffile): - log.error('load_config_begin: Couldn\'t find config file.') + log.error("load_config_begin: Couldn't find config file {}".format( + conffile)) f = Gio.file_new_for_path(conffile) f.load_contents_async(None, self.load_config_finish) return False @@ -194,75 +195,57 @@ def load_config_begin(self): def load_config_finish(self, fp, async_result): try: success, data, etag = fp.load_contents_finish(async_result) - except: - log.error('Couldn\'t open config file') - self.leave() - try: dom = minidom.parseString(data) - except: - log.error('Couldn\'t parse config file') - self.leave() - - conf = dom.getElementsByTagName('configuration') - if not conf: - log.error('No configuration element in config') - self.leave() - - gui = conf[0].getElementsByTagName('gui') - if not gui: - log.error('No gui element in config') - self.leave() - - # find the local syncthing address - address = gui[0].getElementsByTagName('address') - if not address: - log.error('No address element in config') - self.leave() - if not address[0].hasChildNodes(): - log.error('No address specified in config') - self.leave() - self.syncthing_base = 'http://%s' % address[0].firstChild.nodeValue - - # find and fetch the api key - api_key = gui[0].getElementsByTagName('apikey') - if not api_key: - log.error('No api-key element in config') - self.leave() - if not api_key[0].hasChildNodes(): - log.error('No api-key specified in config, please create one via the web interface') - self.leave() - self.api_key = api_key[0].firstChild.nodeValue - - # read device names from config - deviceids = conf[0].getElementsByTagName('device') - try: - for elem in deviceids: - if elem.hasAttribute('name') and elem.hasAttribute('id'): + conf = dom.getElementsByTagName('configuration') + if not conf: + raise Exception('No configuration element in config') + + gui = conf[0].getElementsByTagName('gui') + if not gui: + raise Exception('No gui element in config') + + # Find the local syncthing address + address = gui[0].getElementsByTagName('address') + if not address: + raise Exception('No address element in config') + if not address[0].hasChildNodes(): + raise Exception('No address specified in config') + + self.syncthing_base = 'http://%s' % address[0].firstChild.nodeValue + + # Find and fetch the api key + api_key = gui[0].getElementsByTagName('apikey') + if not api_key: + raise Exception('No api-key element in config') + if not api_key[0].hasChildNodes(): + raise Exception('No api-key specified in config, please create one via the web interface') + self.api_key = api_key[0].firstChild.nodeValue + + # Read folders and devices from config + for elem in conf[0].childNodes: + if elem.nodeType != minidom.Node.ELEMENT_NODE: + continue + if elem.tagName == 'device': self.devices.append({ 'id': elem.getAttribute('id'), 'name': elem.getAttribute('name'), - 'compression': elem.getAttribute('compression'), 'state': '', 'connected': False }) - except: - log.error('config has no devices configured') - self.leave() - - # read folders from config - folders = conf[0].getElementsByTagName('folder') - try: - for elem in folders: - if elem.hasAttribute('id') and elem.hasAttribute('path'): + elif elem.tagName == 'folder': self.folders.append({ 'id': elem.getAttribute('id'), 'path': elem.getAttribute('path'), 'state': 'unknown', }) - except: - log.error('config has no folders configured') + if not self.devices: + raise Exception('No devices in config') + if not self.folders: + raise Exception('No folders in config') + except Exception as e: + log.error('Error parsing config file: {}'.format(e)) self.leave() # Start processes From 117c39168d35acb47a7305583adf035300d38b5f Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 13 Jun 2015 10:33:49 -0400 Subject: [PATCH 43/45] New feature: open file browser when clicking a folder or recent file --- syncthing-ubuntu-indicator.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 8b5690d..358981a 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -615,6 +615,11 @@ def update_files(self): f['folder'], shorten_path(f['file']))) self.current_files_submenu.append(mi) + mi.connect( + 'activate', + self.open_file_browser, + os.path.split( + self.get_full_path(f['folder'], f['file']))[0]) mi.show() self.current_files_menu.show() @@ -640,6 +645,11 @@ def update_files(self): ) ) self.recent_files_submenu.append(mi) + mi.connect( + 'activate', + self.open_file_browser, + os.path.split( + self.get_full_path(f['folder'], f['file']))[0]) mi.show() self.recent_files_menu.show() self.state['update_files'] = False @@ -672,6 +682,7 @@ def update_folders(self): for elm in self.folders: folder_maxlength = max(folder_maxlength, len(elm['id'])) mi = Gtk.MenuItem(elm['id'].ljust(folder_maxlength + 20)) + mi.connect('activate', self.open_file_browser, elm['path']) self.folder_menu_submenu.append(mi) mi.show() else: @@ -804,6 +815,21 @@ def timeout_events(self): GLib.idle_add(self.rest_get, '/rest/events') return True + def open_file_browser(self, menuitem, path): + if not os.path.isdir(path): + log.debug('Not a directory, or does not exist: {}'.format(path)) + return + try: + proc = subprocess.Popen(['xdg-open', path]) + except Exception as e: + log.error("Couldn't open file browser for {} ({})".format(path, e)) + + def get_full_path(self, folder, item): + for elem in self.folders: + if elem['id'] == folder: + a = elem['path'] + return os.path.join(a, item) + if __name__ == '__main__': import signal From 66e7a6520ec9436587502e5b66476bdc67714621 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 13 Jun 2015 10:37:26 -0400 Subject: [PATCH 44/45] Cleanup long lines and typos --- syncthing-ubuntu-indicator.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 358981a..7286bdd 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -273,7 +273,8 @@ def rest_post(self, rest_path): log.debug('rest_post {}'.format(rest_path)) headers = {'X-API-Key': self.api_key} if rest_path in ['/rest/system/restart', '/rest/system/shutdown']: - f = self.session.post(self.syncthing_url(rest_path), headers=headers) + f = self.session.post( + self.syncthing_url(rest_path), headers=headers) return False def rest_get(self, rest_path): @@ -593,7 +594,7 @@ def update_devices(self): self.devices_submenu.append(mi) mi.show() else: - self.devices_menu.set_label('No devices)') + self.devices_menu.set_label('No devices') self.devices_menu.set_sensitive(False) self.state['update_devices'] = False @@ -711,10 +712,10 @@ def count_connected(self): return len([e for e in self.devices if e['connected']]) def syncthing_start(self, *args): - cmd = os.path.join(self.wd, 'start-syncthing.sh') + cmd = [os.path.join(self.wd, 'start-syncthing.sh')] log.info('Starting {}'.format(cmd)) try: - proc = subprocess.Popen([cmd]) + proc = subprocess.Popen(cmd) except Exception as e: log.error("Couldn't run {}: {}".format(cmd, e)) return @@ -743,7 +744,8 @@ def license(self): def show_about(self, widget): dialog = Gtk.AboutDialog() - dialog.set_default_icon_from_file(os.path.join(self.icon_path, 'syncthing-client-idle.svg')) + dialog.set_default_icon_from_file( + os.path.join(self.icon_path, 'syncthing-client-idle.svg')) dialog.set_logo(None) dialog.set_program_name('Syncthing Ubuntu Indicator') dialog.set_version(VERSION) From 2d86214a6dfaf574ba0693b441647f9ef623e7b1 Mon Sep 17 00:00:00 2001 From: hungrywolf27 Date: Sat, 13 Jun 2015 10:38:55 -0400 Subject: [PATCH 45/45] Improve version checking and menu updates --- syncthing-ubuntu-indicator.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/syncthing-ubuntu-indicator.py b/syncthing-ubuntu-indicator.py index 7286bdd..d004f9d 100755 --- a/syncthing-ubuntu-indicator.py +++ b/syncthing-ubuntu-indicator.py @@ -20,7 +20,7 @@ from gi.repository import AppIndicator3 as appindicator from xml.dom import minidom -VERSION = 'v0.3.0' +VERSION = 'v0.3.1' def shorten_path(text, maxlength=80): @@ -510,7 +510,9 @@ def process_rest_system_status(self, data): if data['uptime'] < self.system_data.get('uptime', 0): # Means that Syncthing restarted self.last_seen_id = 0 + GLib.idle_add(self.rest_get, '/rest/system/version') self.system_data = data + # TODO: check status of global announce self.state['update_st_running'] = True def process_rest_system_upgrade(self, data): @@ -704,6 +706,13 @@ def update_st_running(self): self.title_menu.set_label('Could not connect to Syncthing') for dev in self.devices: dev['connected'] = False + self.state['update_devices'] = True + for f in self.folders: + f['state'] = 'unknown' + self.state['update_folders'] = True + self.errors = [] + self.mi_errors.hide() + self.set_state() self.mi_start_syncthing.set_sensitive(True) self.mi_restart_syncthing.set_sensitive(False) self.mi_shutdown_syncthing.set_sensitive(False) @@ -805,7 +814,7 @@ def timeout_rest(self): GLib.idle_add(self.rest_get, '/rest/system/connections') GLib.idle_add(self.rest_get, '/rest/system/status') GLib.idle_add(self.rest_get, '/rest/system/error') - if self.timeout_counter == 0: + if self.timeout_counter == 0 or not self.syncthing_version: GLib.idle_add(self.rest_get, '/rest/system/upgrade') GLib.idle_add(self.rest_get, '/rest/system/version') else: