예제 #1
0
    def __init__(self, agent):
        self.agent = agent
        self.builder = Gtk.Builder()
        self.builder.set_translation_domain('btsync-gui')
        self.builder.add_from_file(
            os.path.dirname(__file__) + "/btsyncstatus.glade")
        self.builder.connect_signals(self)
        self.menu = self.builder.get_object('btsyncmenu')
        self.menuconnection = self.builder.get_object('connectionitem')
        self.menustatus = self.builder.get_object('statusitem')
        self.menupause = self.builder.get_object('pausesyncing')
        self.menudebug = self.builder.get_object('setdebug')
        self.menuopenweb = self.builder.get_object('openweb')
        self.menuopenapp = self.builder.get_object('openapp')
        self.about = self.builder.get_object('aboutdialog')

        self.init_icons()

        self.ind = TrayIndicator('btsync', self.icn_disconnected)

        if agent.is_auto():
            self.menuconnection.set_visible(False)
            self.ind.set_title(_('BitTorrent Sync'))
            self.ind.set_tooltip_text(_('BitTorrent Sync Status Indicator'))
        else:
            self.menuconnection.set_label('{0}:{1}'.format(
                agent.get_host(), agent.get_port()))
            self.ind.set_title(
                _('BitTorrent Sync {0}:{1}').format(agent.get_host(),
                                                    agent.get_port()))
            self.ind.set_tooltip_text(
                _('BitTorrent Sync {0}:{1}').format(agent.get_host(),
                                                    agent.get_port()))

        self.ind.set_menu(self.menu)
        self.ind.set_default_action(self.onActivate)
        self.refresh_menus()

        # icon animator
        self.frame = 0
        self.rotating = False
        self.transferring = False
        self.animator_id = None

        # application window
        self.app = None

        # other variables
        self.connection = BtSyncStatus.DISCONNECTED
        self.connect_id = None
        self.status_to = BtDynamicTimeout(1000, self.btsync_refresh_status)
예제 #2
0
	def __init__(self,agent,status=None):
		self.agent	= agent
		self.status	= status

		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncapp.glade")
		self.builder.connect_signals (self)

		width, height = self.agent.get_pref('windowsize', (602,328))

		self.window = self.builder.get_object('btsyncapp')
		self.window.set_default_size(width, height)
		self.window.connect('delete-event',self.onDelete)
		if not self.agent.is_auto():
			title = self.window.get_title()
			self.window.set_title('{0} - ({1}:{2})'.format(
				title,
				agent.get_host(),
				agent.get_port()
			))
		self.window.show()

		self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
		self.app_status_to = BtDynamicTimeout(1000,self.refresh_app_status)
		self.dlg = None

		self.prefs = self.agent.get_prefs()

		self.init_folders_controls()
		self.init_devices_controls()
		self.init_transfers_controls()
		self.init_history_controls()
		self.init_preferences_controls()
		self.init_settings_controls()

		# TODO: Hide pages not supported by API
		notebook = self.builder.get_object('notebook1')
		transfers = notebook.get_nth_page(2)
		history = notebook.get_nth_page(3)
		transfers.hide()
		history.hide()
		# TODO: End

		self.init_transfer_status()

		self.init_folders_values()
		self.init_preferences_values()
		self.init_settings_values()
예제 #3
0
	def __init__(self,agent):
		self.agent = agent

		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncapp.glade")
		self.builder.connect_signals (self)

		width, height = self.agent.get_pref('windowsize', (602,328))

		self.window = self.builder.get_object('btsyncapp')
		self.window.set_default_size(width, height)
		self.window.connect('delete-event',self.onDelete)
		if not self.agent.is_auto():
			title = self.window.get_title()
			self.window.set_title('{0} - ({1}:{2})'.format(
				title,
				agent.get_host(),
				agent.get_port()
			))
		self.window.show()

		self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
		self.app_status_to = BtDynamicTimeout(1000,self.refresh_app_status)
		self.dlg = None

		self.prefs = self.agent.get_prefs()

		self.init_folders_controls()
		self.init_devices_controls()
		self.init_transfers_controls()
		self.init_history_controls()
		self.init_preferences_controls()
		self.init_folders_values()
		self.init_preferences_values()
예제 #4
0
	def __init__(self,agent):
		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncstatus.glade")
		self.builder.connect_signals (self)
		self.menu = self.builder.get_object('btsyncmenu')
		self.menuconnection = self.builder.get_object('connectionitem')
		self.menustatus = self.builder.get_object('statusitem')
		self.menupause = self.builder.get_object('pausesyncing')
		self.menudebug = self.builder.get_object('setdebug')
		self.menuopenweb = self.builder.get_object('openweb')
		self.menuopenapp = self.builder.get_object('openapp')
		self.about = self.builder.get_object('aboutdialog')
		if agent.dark:
			self.icn_disconnected = 'btsync-gui-disconnected-dark'
			self.icn_connecting = 'btsync-gui-connecting-dark'
			self.icn_paused = 'btsync-gui-paused-dark'
			self.icn_idle = 'btsync-gui-0-dark'
			self.icn_activity = 'btsync-gui-{0}-dark'
		else:
			self.icn_disconnected = 'btsync-gui-disconnected'
			self.icn_connecting = 'btsync-gui-connecting'
			self.icn_paused = 'btsync-gui-paused'
			self.icn_idle = 'btsync-gui-0'
			self.icn_activity = 'btsync-gui-{0}'


		self.ind = TrayIndicator (
			'btsync',
			self.icn_disconnected
		)
		if agent.is_auto():
			self.menuconnection.set_visible(False)
			self.ind.set_title(_('BitTorrent Sync'))
			self.ind.set_tooltip_text(_('BitTorrent Sync Status Indicator'))
		else:
			self.menuconnection.set_label('{0}:{1}'.format(agent.get_host(),agent.get_port()))
			self.ind.set_title(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
			self.ind.set_tooltip_text(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
		self.menuopenweb.set_visible(agent.is_webui())
		self.ind.set_menu(self.menu)
		self.ind.set_default_action(self.onActivate)

		# icon animator
		self.frame = 0
		self.rotating = False
		self.transferring = False
		self.animator_id = None

		# application window
		self.app = None

		# other variables
		self.connection = BtSyncStatus.DISCONNECTED
		self.connect_id = None
		self.status_to = BtDynamicTimeout(1000,self.btsync_refresh_status)
		self.agent = agent
예제 #5
0
class BtSyncApp(BtInputHelper,BtMessageHelper):

	def __init__(self,agent):
		self.agent = agent

		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncapp.glade")
		self.builder.connect_signals (self)

		width, height = self.agent.get_pref('windowsize', (602,328))

		self.window = self.builder.get_object('btsyncapp')
		self.window.set_default_size(width, height)
		self.window.connect('delete-event',self.onDelete)
		if not self.agent.is_auto():
			title = self.window.get_title()
			self.window.set_title('{0} - ({1}:{2})'.format(
				title,
				agent.get_host(),
				agent.get_port()
			))
		self.window.show()

		self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
		self.app_status_to = BtDynamicTimeout(1000,self.refresh_app_status)
		self.dlg = None

		self.prefs = self.agent.get_prefs()

		self.init_folders_controls()
		self.init_devices_controls()
		self.init_transfers_controls()
		self.init_history_controls()
		self.init_preferences_controls()

		# TODO: Hide pages not supported by API
		notebook = self.builder.get_object('notebook1')
		transfers = notebook.get_nth_page(2)
		history = notebook.get_nth_page(3)
		transfers.hide()
		history.hide()
		# TODO: End

		self.init_transfer_status()

		self.init_folders_values()
		self.init_preferences_values()

	def close(self):
		self.app_status_to.stop()
		if self.dlg is not None:
			self.dlg.response(Gtk.ResponseType.CANCEL)

	def connect_close_signal(self,handler):
		return self.window.connect('delete-event', handler)

	def init_folders_controls(self):
		self.folders = self.builder.get_object('folders_list')
		self.folders_menu = self.builder.get_object('folders_menu')
		self.folders_menu_openfolder = self.builder.get_object('folder_menu_openfolder')
		self.folders_menu_openarchive = self.builder.get_object('folder_menu_openarchive')
		self.folders_menu_editsyncignore = self.builder.get_object('folder_menu_editsyncignore')
		self.folders_selection = self.builder.get_object('folders_selection')
		self.folders_treeview = self.builder.get_object('folders_tree_view')
		self.folders_activity_label = self.builder.get_object('folders_activity_label')
		self.folders_add = self.builder.get_object('folders_add')
		self.folders_remove = self.builder.get_object('folders_remove')
		self.folders_remove.set_sensitive(False)
		self.set_treeview_column_widths(
			self.folders_treeview,
			self.agent.get_pref('folders_columns',[300])
		)
		self.set_treeview_sort_info(
			self.folders_treeview,
			self.agent.get_pref('folders_sortinfo', [0, Gtk.SortType.ASCENDING])
		)

	def init_folders_values(self):
		try:
			self.lock()
			folders = self.agent.get_folders()
			if folders is not None:
				for index, value in enumerate(folders):
					# see in update_folder_values the insane explanation why
					# also an md5 digest has to be saved
					digest = md5.new(value['dir'].encode('latin-1')).hexdigest()
					self.folders.append ([
						self.agent.fix_decode(value['dir']),		# 0:Folder
						self.get_folder_info_string(value),			# 1:Content
						value['secret'],							# 2:Secret
						digest,										# 3:FolderTag
						Pango.EllipsizeMode.END						# 4:EllipsizeMode
					])
					self.add_device_infos(value,digest)
			self.unlock()
			self.app_status_to.start()
		except requests.exceptions.ConnectionError:
			self.unlock()
			self.onConnectionError()
		except requests.exceptions.HTTPError:
			self.unlock()
			self.onCommunicationError()

	def init_devices_controls(self):
		self.devices = self.builder.get_object('devices_list')
		self.devices_treeview = self.builder.get_object('devices_tree_view')
		self.devices_activity_label = self.builder.get_object('devices_activity_label')
		self.set_treeview_column_widths(
			self.devices_treeview,self.agent.get_pref('devices_columns',[150,300])
		) 
		self.set_treeview_sort_info(
			self.devices_treeview,
			self.agent.get_pref('devices_sortinfo', [0, Gtk.SortType.ASCENDING])
		)

	def init_transfers_controls(self):
		self.transfers = self.builder.get_object('transfers_list')
		self.transfers_treeview = self.builder.get_object('transfers_tree_view')
		self.transfers_activity_label = self.builder.get_object('transfers_activity_label')
		# TODO: remove placeholder as soon as the suitable API call permits
		#       a working implementation...
		self.transfers.append ([
			_('Cannot implement due to missing API'),	# 0:
			_('BitTorrent Inc.'),						# 1:
			'',											# 2:
			'',											# 3:
			Pango.EllipsizeMode.END						# 4:EllipsizeMode
		])
		self.set_treeview_column_widths(
			self.transfers_treeview,self.agent.get_pref('transfers_columns',[300,150,80])
		) 

	def init_history_controls(self):
		self.history = self.builder.get_object('history_list')
		self.history_treeview = self.builder.get_object('history_tree_view')
		self.history_activity_label = self.builder.get_object('history_activity_label')
		# TODO: remove placeholder as soon as the suitable API call permits
		#       a working implementation...
		self.history.append ([
			_('Now'),									# 0:
			_('Cannot implement due to missing API'),	# 1:
			Pango.EllipsizeMode.END						# 4:EllipsizeMode
		])
		self.set_treeview_column_widths(
			self.history_treeview,self.agent.get_pref('history_columns',[150])
		) 

	def refresh_app_status(self):
		try:
			self.lock()
			folders = self.agent.get_folders()
			# forward scan updates existing folders and adds new ones
			for index, value in enumerate(folders):
				# see in update_folder_values the insane explanation why
				# also an md5 digest has to be saved
				digest = md5.new(value['dir'].encode('latin-1')).hexdigest()
				if not self.update_folder_values(value):
					# it must be new (probably added via web interface) - let's add it
					self.folders.append ([
						self.agent.fix_decode(value['dir']),	# 0:Folder
						self.get_folder_info_string(value),			# 1:Content
						value['secret'],							# 2:Secret
						digest,										# 3:FolderTag
						Pango.EllipsizeMode.END						# 4:EllipsizeMode
					])
				self.update_device_infos(value,digest)
			# reverse scan deletes disappeared folders...
			for row in self.folders:
				if not self.folder_exists(folders,row):
					self.folders.remove(row.iter)
					self.remove_device_infos(row[2],row[3])
			# update transfer status
			self.update_transfer_status(self.agent.get_speed())
			# TODO: fill file list...
			#       but there is still no suitable API call...
			self.unlock()
			return True
		except requests.exceptions.ConnectionError:
			self.unlock()
			return self.onConnectionError()
		except requests.exceptions.HTTPError:
			self.unlock()
			return self.onCommunicationError()

	def init_transfer_status(self):
		self.update_transfer_status({'upload':0,'download':0})

	def update_transfer_status(self,speed):
		activity = _('{0:.1f} kB/s up, {1:.1f} kB/s down').format(speed['upload'] / 1000, speed['download'] / 1000)
		self.folders_activity_label.set_label(activity)
		self.devices_activity_label.set_label(activity)
		self.transfers_activity_label.set_label(activity)
		self.history_activity_label.set_label(activity)

	def update_folder_values(self,value):
		for row in self.folders:
			if value['secret'] == row[2]:
				# found - update information
				row[1] = self.get_folder_info_string(value)
				return True
			elif md5.new(value['dir'].encode('latin-1')).hexdigest() == row[3]:
				# comparing the md5 digests avoids casting errors due to the
				# insane encoding fix tecnique
				# found - secret was changed
				row[1] = self.get_folder_info_string(value)
				row[2] = value['secret']
				return True
		# not found
		return False

	def folder_exists(self,folders,row):
		if folders is not None:
			for index, value in enumerate(folders):
				if value['secret'] == row[2]:
					return True
				elif md5.new(value['dir'].encode('latin-1')).hexdigest() == row[3]:
					# comparing the md5 digests avoids casting errors due to the
					# insane encoding fix tecnique
					return True
		return False

	def add_device_infos(self,folder,digest):
		foldername = self.agent.fix_decode(folder['dir'])
		peers = self.agent.get_folder_peers(folder['secret'])
		for index, value in enumerate(peers):
			self.devices.append ([
				self.agent.fix_decode(value['name']),		# 0:Device
				foldername,									# 1:Folder
				self.get_device_info_string(value),			# 2:Status
				folder['secret'],							# 3:Secret
				digest,										# 4:FolderTag
				value['id'],								# 5:DeviceTag
				self.get_device_info_icon_name(value),		# 6:ConnectionIconName
				Pango.EllipsizeMode.END						# 7:EllipsizeMode
			])

	def update_device_infos(self,folder,digest):
		foldername = self.agent.fix_decode(folder['dir'])
		peers = self.agent.get_folder_peers(folder['secret'])
		# forward scan updates existing and adds new
		for index, value in enumerate(peers):
			if not self.update_device_values(folder,value,digest):
				# it must be new - let's add it
					self.devices.append ([
					self.agent.fix_decode(value['name']),		# 0:Device
					foldername,									# 1:Folder
					self.get_device_info_string(value),			# 2:Status
					folder['secret'],							# 3:Secret
					digest,										# 4:FolderTag
					value['id'],								# 5:DeviceTag
					self.get_device_info_icon_name(value),		# 6:ConnectionIconName
					Pango.EllipsizeMode.END						# 7:EllipsizeMode
				])
		# reverse scan deletes disappeared folders...
		for row in self.devices:
			if row[3] == folder['secret'] or row[4] == digest:
				# it's our folder
				if not self.device_exists(peers,row):
					self.devices.remove(row.iter)

	def update_device_values(self,folder,peer,digest):
		for row in self.devices:
			if peer['id'] == row[5] and folder['secret'] == row[3]:
				# found - update information
				row[0] = self.agent.fix_decode(peer['name'])
				row[2] = self.get_device_info_string(peer)
				row[6] = self.get_device_info_icon_name(peer)
				return True
			elif peer['id'] == row[5] and digest == row[4]:
				# found - secret probably changed...
				row[0] = self.agent.fix_decode(peer['name'])
				row[2] = self.get_device_info_string(peer)
				row[3] = folder['secret']
				row[6] = self.get_device_info_icon_name(peer)
				return True
		# not found
		return False

	def remove_device_infos(self,secret,digest=None):
		for row in self.devices:
			if secret == row[3]:
				self.devices.remove(row.iter)
			elif digest is not None and digest == row[4]:
				self.devices.remove(row.iter)

	def device_exists(self,peers,row):
		for index, value in enumerate(peers):
			if value['id'] == row[5]:
				return True
		return False

	def get_folder_info_string(self,folder):
		if folder['error'] == 0:
			if folder['indexing'] == 0:
				return _('{0} in {1} files').format(self.sizeof_fmt(folder['size']), str(folder['files']))
			else:
				return _('{0} in {1} files (indexing...)').format(self.sizeof_fmt(folder['size']), str(folder['files']))
		else:
			return self.agent.get_error_message(folder)

	def get_device_info_icon_name(self,peer):
		return {
			'direct' : 'btsync-gui-direct',
			'relay'  : 'btsync-gui-cloud'
		}.get(peer['connection'], 'btsync-gui-unknown')

	def get_device_info_string(self,peer):
		if peer['synced'] != 0:
			dt = datetime.datetime.fromtimestamp(peer['synced'])
			return _('Synced on {0}').format(dt.strftime("%x %X"))
		elif peer['download'] == 0 and peer['upload'] != 0:
			return _('⇧ {0}').format(self.sizeof_fmt(peer['upload']))
		elif peer['download'] != 0 and peer['upload'] == 0:
			return _('⇩ {0}').format(self.sizeof_fmt(peer['download']))
		elif peer['download'] != 0 and peer['upload'] != 0:
			return _('⇧ {0} - ⇩ {1}').format(self.sizeof_fmt(peer['upload']), self.sizeof_fmt(peer['download']))
		else:
			return _('Idle...')

	def init_preferences_controls(self):
		self.devname = self.builder.get_object('devname')
		self.autostart = self.builder.get_object('autostart')
		self.listeningport = self.builder.get_object('listeningport')
		self.upnp = self.builder.get_object('upnp')
		self.limitdn = self.builder.get_object('limitdn')
		self.limitdnrate = self.builder.get_object('limitdnrate')
		self.limitup = self.builder.get_object('limitup')
		self.limituprate = self.builder.get_object('limituprate')

	def init_preferences_values(self):
		self.lock()
		self.attach(self.devname,BtValueDescriptor.new_from('device_name',self.prefs['device_name']))
		# self.autostart.set_active(self.prefs[""]);
		self.autostart.set_sensitive(False)
		self.attach(self.listeningport,BtValueDescriptor.new_from('listening_port',self.prefs['listening_port']))
		self.attach(self.upnp,BtValueDescriptor.new_from('use_upnp',self.prefs['use_upnp']))
		self.attach(self.limitdnrate,BtValueDescriptor.new_from('download_limit',self.prefs['download_limit']))
		self.attach(self.limituprate,BtValueDescriptor.new_from('upload_limit',self.prefs['upload_limit']))

		self.limitdn.set_active(self.prefs['download_limit'] > 0)
		self.limitup.set_active(self.prefs['upload_limit'] > 0)
		self.unlock()

	def get_treeview_column_widths(self,treewidget):
		columns = treewidget.get_columns()
		widths = []
		for index, value in enumerate(columns):
			widths.append(value.get_width())
		return widths

	def set_treeview_column_widths(self,treewidget,widths):
		columns = treewidget.get_columns()
		for index, value in enumerate(columns):
			if index < len(widths):
				value.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
				value.set_fixed_width(max(widths[index],32))

	def get_treeview_sort_info(self,treewidget):
		treemodel = treewidget.get_model()
		column_id, sort_order = treemodel.get_sort_column_id()
		return [column_id, int(sort_order)]

	def set_treeview_sort_info(self,treewidget,sortinfo):
		if sortinfo[0] is not None:
			treemodel = treewidget.get_model()
			treemodel.set_sort_column_id(sortinfo[0],sortinfo[1])
			columns = treewidget.get_columns()
			for index, value in enumerate(columns):
				if value.get_sort_column_id() == sortinfo[0]:
					value.set_sort_order(sortinfo[1])
					value.set_sort_indicator(True)
					return

	def onDelete(self, *args):
		width, height = self.window.get_size()
		self.agent.set_pref('windowsize', (width, height))
		self.agent.set_pref('folders_columns', self.get_treeview_column_widths(self.folders_treeview))
		self.agent.set_pref('devices_columns', self.get_treeview_column_widths(self.devices_treeview))
		self.agent.set_pref('transfers_columns', self.get_treeview_column_widths(self.transfers_treeview))
		self.agent.set_pref('history_columns', self.get_treeview_column_widths(self.history_treeview))
		self.agent.set_pref('folders_sortinfo', self.get_treeview_sort_info (self.folders_treeview))
		self.agent.set_pref('devices_sortinfo', self.get_treeview_sort_info (self.devices_treeview))
		self.close()

	def onSaveEntry(self,widget,valDesc,newValue):
		try:
			self.agent.set_prefs({valDesc.Name : newValue})
			self.prefs[valDesc.Name] = newValue
		except requests.exceptions.ConnectionError:
			return self.onConnectionError()
		except requests.exceptions.HTTPError:
			return self.onCommunicationError()
		return True

	def onFoldersSelectionChanged(self,selection):
		model, tree_iter = selection.get_selected()
		self.folders_remove.set_sensitive(selection.count_selected_rows() > 0)

	def onFoldersAdd(self,widget):
		self.dlg = BtSyncFolderAdd(self.agent)
		try:
			self.dlg.create()
			result = self.dlg.run()
			if result == Gtk.ResponseType.OK:
				# all checks have already been done. let's go!
				result = self.agent.add_folder(self.dlg.folder,self.dlg.secret)
				if self.agent.get_error_code(result) > 0:
					self.show_warning(self.window,self.agent.get_error_message(result))
		except requests.exceptions.ConnectionError:
			pass
		except requests.exceptions.HTTPError:
			pass
		finally:
			self.dlg.destroy()
			self.dlg = None

	def onFoldersRemove(self,widget):
		self.dlg = BtSyncFolderRemove()
		self.dlg.create()
		result = self.dlg.run()
		self.dlg.destroy()
		if result == Gtk.ResponseType.OK:
			model, tree_iter = self.folders_selection.get_selected()
			if tree_iter is not None:
				# ok - let's delete it!
				secret = model[tree_iter][2]
				try:
					result = self.agent.remove_folder(secret)
					if self.agent.get_error_code(result) == 0:
						self.folders.remove(tree_iter)
						self.remove_device_infos(secret)
					else:
						logging.error('Failed to remove folder ' + str(secret))
				except requests.exceptions.ConnectionError:
					pass
				except requests.exceptions.HTTPError:
					pass

	def onFoldersMouseClick(self,widget,event):
		x = int(event.x)
		y = int(event.y)
		time = event.time
		pathinfo = widget.get_path_at_pos(x,y)
		if pathinfo is not None:
			if event.button == 1:
				if event.type == Gdk.EventType._2BUTTON_PRESS or event.type == Gdk.EventType._3BUTTON_PRESS:
					path, column, cellx, celly = pathinfo
					widget.grab_focus()
					widget.set_cursor(path,column,0)
					model, tree_iter = self.folders_selection.get_selected()
					if tree_iter is not None:
						if os.path.isdir(model[tree_iter][0]):
							os.system('xdg-open "{0}"'.format(model[tree_iter][0]))
							return True
			elif event.button == 3:
				path, column, cellx, celly = pathinfo
				widget.grab_focus()
				widget.set_cursor(path,column,0)
				model, tree_iter = self.folders_selection.get_selected()
				if self.agent.is_local() and tree_iter is not None:
					self.folders_menu_openfolder.set_sensitive(
						os.path.isdir(model[tree_iter][0])
					)
					self.folders_menu_openarchive.set_sensitive(
						os.path.isdir(model[tree_iter][0] + '/.SyncArchive')
					)
					self.folders_menu_editsyncignore.set_sensitive(
						os.path.isfile(model[tree_iter][0] + '/.SyncIgnore')
					)
				else:
					self.folders_menu_openfolder.set_sensitive(False)
					self.folders_menu_openarchive.set_sensitive(False)
					self.folders_menu_editsyncignore.set_sensitive(False)

				self.folders_menu.popup(None,None,None,None,event.button,time)
				return True

	def onFoldersCopySecret(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			self.clipboard.set_text(model[tree_iter][2], -1)

	def onFoldersConnectMobile(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			result = self.agent.get_secrets(model[tree_iter][2], False)
			if self.agent.get_error_code(result) == 0:
				self.dlg = BtSyncFolderScanQR(
					result['read_write'] if result.has_key('read_write') else None,
					result['read_only'],
					os.path.basename(model[tree_iter][0])
				)
				self.dlg.create()
				result = self.dlg.run()
				self.dlg.destroy()
				self.dlg = None

	def onFoldersOpenFolder(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			if os.path.isdir(model[tree_iter][0]):
				os.system('xdg-open "{0}"'.format(model[tree_iter][0]))

	def onFoldersOpenArchive(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			syncarchive = model[tree_iter][0] + '/.SyncArchive'
			if os.path.isdir(syncarchive):
				os.system('xdg-open "{0}"'.format(syncarchive))

	def onFoldersEditSyncIgnore(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			syncignore = model[tree_iter][0] + '/.SyncIgnore'
			if os.path.isfile(syncignore):
				os.system('xdg-open "{0}"'.format(syncignore))

	def onFoldersPreferences(self,widget):
		model, tree_iter = self.folders_selection.get_selected()
		if tree_iter is not None:
			self.dlg = BtSyncFolderPrefs(self.agent)
			try:
				self.dlg.create(model[tree_iter][0],model[tree_iter][2])
				self.dlg.run()
			except requests.exceptions.ConnectionError:
				pass
			except requests.exceptions.HTTPError:
				pass
			finally:
				self.dlg.destroy()
				self.dlg = None

	def onPreferencesToggledLimitDn(self,widget):
		self.limitdnrate.set_sensitive(widget.get_active())
		if not self.is_locked():
			rate = int(self.limitdnrate.get_text()) if widget.get_active() else 0
			try:
				self.agent.set_prefs({"download_limit" : rate})
				self.prefs['download_limit'] = rate
			except requests.exceptions.ConnectionError:
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				return self.onCommunicationError()


	def onPreferencesToggledLimitUp(self,widget):
		self.limituprate.set_sensitive(widget.get_active())
		if not self.is_locked():
			rate = int(self.limituprate.get_text()) if widget.get_active() else 0
			try:
				self.agent.set_prefs({"upload_limit" : rate})
				self.prefs['upload_limit'] = rate
			except requests.exceptions.ConnectionError:
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				return self.onCommunicationError()

	def onPreferencesClickedAdvanced(self,widget):
		try:
			self.dlg = BtSyncPrefsAdvanced(self.agent)
			self.dlg.run()
		except requests.exceptions.ConnectionError:
			logging.error('BtSync API Connection Error')
		except requests.exceptions.HTTPError:
			logging.error('BtSync API HTTP error: {0}'.format(self.agent.get_status_code()))
		except Exception as e:
			# this should not really happen...
			logging.error('onPreferencesClickedAdvanced: Unexpected exception caught: '+str(e))
		finally:
			if isinstance(self.dlg, BtSyncPrefsAdvanced):
				self.dlg.destroy()
			self.dlg = None

	def onConnectionError(self):
		logging.error('BtSync API Connection Error')
		self.window.destroy()
		return False

	def onCommunicationError(self):
		logging.error('BtSync API HTTP error: {0}'.format(self.agent.get_status_code()))
		self.window.destroy()
		return False
예제 #6
0
class BtSyncStatus:
	DISCONNECTED	= 0
	CONNECTING		= 1
	CONNECTED		= 2
	PAUSED			= 3

	def __init__(self,agent):
		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncstatus.glade")
		self.builder.connect_signals (self)
		self.menu = self.builder.get_object('btsyncmenu')
		self.menuconnection = self.builder.get_object('connectionitem')
		self.menustatus = self.builder.get_object('statusitem')
		self.menupause = self.builder.get_object('pausesyncing')
		self.menudebug = self.builder.get_object('setdebug')
		self.menuopenweb = self.builder.get_object('openweb')
		self.menuopenapp = self.builder.get_object('openapp')
		self.about = self.builder.get_object('aboutdialog')


		self.ind = TrayIndicator (
			'btsync',
			'btsync-gui-disconnected'
		)
		if agent.is_auto():
			self.menuconnection.set_visible(False)
			self.ind.set_title(_('BitTorrent Sync'))
			self.ind.set_tooltip_text(_('BitTorrent Sync Status Indicator'))
		else:
			self.menuconnection.set_label('{0}:{1}'.format(agent.get_host(),agent.get_port()))
			self.ind.set_title(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
			self.ind.set_tooltip_text(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
		self.menuopenweb.set_visible(agent.is_webui())
		self.ind.set_menu(self.menu)
		self.ind.set_default_action(self.onActivate)

		# icon animator
		self.frame = 0
		self.rotating = False
		self.transferring = False
		self.animator_id = None

		# application window
		self.app = None

		# other variables
		self.connection = BtSyncStatus.DISCONNECTED
		self.connect_id = None
		self.status_to = BtDynamicTimeout(1000,self.btsync_refresh_status)
		self.agent = agent

	def startup(self):
		self.btsyncver = { 'version': '0.0.0' }
		# status
		if self.agent.is_auto():
			self.menupause.set_sensitive(self.agent.is_auto())
			if self.agent.is_paused():
				self.set_status(BtSyncStatus.PAUSED)
				self.menupause.set_active(True)
			else:
				self.set_status(BtSyncStatus.CONNECTING)
				self.menupause.set_active(False)
				self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		else:
			self.set_status(BtSyncStatus.CONNECTING)
			self.menupause.set_sensitive(False)
			self.menupause.set_active(False)
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		
	def shutdown(self):
		if self.animator_id is not None:
			GObject.source_remove(self.animator_id)
		if self.connect_id is not None:
			GObject.source_remove(self.connect_id)
		self.status_to.stop()

	def open_app(self):
		if isinstance(self.app, BtSyncApp):
			self.app.window.present()
		else:
			try:
				self.app = BtSyncApp(self.agent)
				self.app.connect_close_signal(self.onDeleteApp)
			except requests.exceptions.ConnectionError:
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				return self.onCommunicationError()

	def close_app(self,stillopen=True):
		if isinstance(self.app, BtSyncApp):
			if stillopen:
				self.app.close()
				# self.app.window.close()
				self.app.window.destroy()
			del self.app
			self.app = None

	def btsync_connect(self):
		if self.connection is BtSyncStatus.DISCONNECTED or \
			self.connection is BtSyncStatus.CONNECTING or \
			self.connection is BtSyncStatus.PAUSED:
			try:
				self.set_status(BtSyncStatus.CONNECTING)
				self.menustatus.set_label(_('Connecting...'))
				version = self.agent.get_version()
				self.btsyncver = version
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Idle'))
				self.status_to.start()
				self.connect_id = None
				return False

			except requests.exceptions.ConnectionError:
				self.connect_id = None
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				self.connect_id = None
				return self.onCommunicationError()


		else:
			logging.info('Cannot connect since I\'m already connected')
		

	def btsync_refresh_status(self):
		if self.connection is not BtSyncStatus.CONNECTED:
			logging.info('Interrupting refresh sequence...')
			return False
		logging.info('Refresh status...')
		indexing = False
		transferring = False
		try:
			folders = self.agent.get_folders()
			for fIndex, fValue in enumerate(folders):
				if fValue['indexing'] > 0:
					indexing = True
# this takes too much resources...
#				peers = self.agent.get_folder_peers(fValue['secret'])
#				for pIndex, pValue in enumerate(peers):
#					if long(pValue['upload']) + long(pValue['download']) > 0:
#						transferring = True
#####
			speed = self.agent.get_speed()
			if transferring or speed['upload'] > 0 or speed['download'] > 0:
				# there are active transfers...
				self.set_status(BtSyncStatus.CONNECTED,True)
				self.menustatus.set_label(_('{0:.1f} kB/s up, {1:.1f} kB/s down').format(speed['upload'] / 1000, speed['download'] / 1000))
			elif indexing:
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Indexing...'))
			else:
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Idle'))
			return True
	
		except requests.exceptions.ConnectionError:
			return self.onConnectionError()
		except requests.exceptions.HTTPError:
			return self.onCommunicationError()

	def set_status(self,connection,transferring=False):
		if connection is BtSyncStatus.DISCONNECTED:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-disconnected')
			self.menudebug.set_sensitive(False)
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		elif connection is BtSyncStatus.CONNECTING:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-connecting')
			self.menudebug.set_sensitive(False)
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		elif connection is BtSyncStatus.PAUSED:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-paused')
			self.menudebug.set_sensitive(self.agent.is_local())
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		else:
			self.menudebug.set_sensitive(self.agent.is_local())
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(True)
			self.menuopenweb.set_sensitive(True)
			if transferring and not self.transferring:
				if not self.rotating:
					# initialize animation
					self.transferring = True
					self.frame = 0
					self.animator_id = GObject.timeout_add(200, self.onIconRotate)
			self.transferring = transferring
			if not self.transferring:
				self.ind.set_from_icon_name('btsync-gui-0')
		self.connection = connection

	def show_status(self,statustext):
		self.menustatus.set_label(statustext)

	def is_connected(self):
		return self.connection is BtSyncStatus.CONNECTED

	def onActivate(self,widget):
#		self.menu.popup(None,None,Gtk.StatusIcon.position_menu,widget,3,0)
		if self.is_connected():
			self.open_app()

	def onAbout(self,widget):
		self.about.set_version(_('Version {0} ({0})').format(self.btsyncver['version']))
		self.about.set_comments(_('Linux UI Version {0}').format(VERSION))
		self.about.show()
		self.about.run()
		self.about.hide()

	def onOpenApp(self,widget):
		self.open_app()

	def onOpenWeb(self,widget):
		webbrowser.open('http://{0}:{1}@{2}:{3}'.format(
			urllib.quote(self.agent.get_username(),''),
			urllib.quote(self.agent.get_password(),''),
			self.agent.get_host(),
			self.agent.get_port()
		), 2)

	def onDeleteApp(self, *args):
		self.close_app(False)

	def onSendFeedback(self,widget):
		webbrowser.open(
			'http://forum.bittorrent.com/topic/28106-linux-desktop-gui-unofficial-packages-for-bittorrent-sync/',
			2
		)

	def onOpenManual(self,widget):
		os.system('xdg-open "/usr/share/doc/btsync-common/BitTorrentSyncUserGuide.pdf.gz"')

	def onTogglePause(self,widget):
		if widget.get_active() and not self.agent.is_paused():
			logging.info('Suspending agent...')
			self.close_app();
			self.set_status(BtSyncStatus.PAUSED)
			self.agent.suspend()
		elif not widget.get_active() and self.agent.is_paused():
			logging.info('Resuming agent...')
			self.set_status(BtSyncStatus.CONNECTING)
			self.agent.resume()
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)

	def onToggleLogging(self,widget):
		if self.is_connected():
			if widget.get_active() and not self.agent.get_debug():
				logging.info('Activate logging...')
				self.agent.set_debug(True)
			elif not widget.get_active() and self.agent.get_debug():
				logging.info('Disable logging...')
				self.agent.set_debug(False)

	def onQuit(self,widget):
		Gtk.main_quit()

	def onIconRotate(self):
		if self.frame == -1:
			# immediate stop
			self.frame = 0
			self.rotating = False
			self.animator_id = None
			return False
		elif not self.transferring and self.frame % 12 == 0:
			# do not stop immediately - wait for the
			# cycle to finish.
			self.ind.set_from_icon_name('btsync-gui-0')
			self.rotating = False
			self.frame = 0
			self.animator_id = None
			return False
		else:
			self.ind.set_from_icon_name('btsync-gui-{0}'.format(self.frame % 12))
			self.rotating = True
			self.frame += 1
			return True

	def onConnectionError(self):
		self.set_status(BtSyncStatus.DISCONNECTED)
		self.menustatus.set_label(_('Disconnected'))
		self.close_app();
		logging.info('BtSync API Connection Error')
		if self.agent.is_auto() and not self.agent.is_running():
			logging.warning('BitTorrent Sync seems to be crashed. Restarting...')
			self.agent.start_agent()
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		else:
			self.connect_id = GObject.timeout_add(5000, self.btsync_connect)
		return False

	def onCommunicationError(self):
		self.set_status(BtSyncStatus.DISCONNECTED)
		self.menustatus.set_label(_('Disconnected: Communication Error {0}').format(self.agent.get_status_code()))
		self.close_app();
		logging.warning('BtSync API HTTP error: {0}'.format(self.agent.get_status_code()))
		self.connect_id = GObject.timeout_add(5000, self.btsync_connect)
		return False
예제 #7
0
class BtSyncApp(BtInputHelper, BtMessageHelper):
    def __init__(self, agent):
        self.agent = agent

        self.builder = Gtk.Builder()
        self.builder.set_translation_domain('btsync-gui')
        self.builder.add_from_file(
            os.path.dirname(__file__) + "/btsyncapp.glade")
        self.builder.connect_signals(self)

        width, height = self.agent.get_pref('windowsize', (602, 328))

        self.window = self.builder.get_object('btsyncapp')
        self.window.set_default_size(width, height)
        self.window.connect('delete-event', self.onDelete)
        if not self.agent.is_auto():
            title = self.window.get_title()
            self.window.set_title('{0} - ({1}:{2})'.format(
                title, agent.get_host(), agent.get_port()))
        self.window.show()

        self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
        self.app_status_to = BtDynamicTimeout(1000, self.refresh_app_status)
        self.dlg = None

        self.prefs = self.agent.get_prefs()

        self.init_folders_controls()
        self.init_devices_controls()
        self.init_transfers_controls()
        self.init_history_controls()
        self.init_preferences_controls()

        # TODO: Hide pages not supported by API
        notebook = self.builder.get_object('notebook1')
        transfers = notebook.get_nth_page(2)
        history = notebook.get_nth_page(3)
        transfers.hide()
        history.hide()
        # TODO: End

        self.init_transfer_status()

        self.init_folders_values()
        self.init_preferences_values()

    def close(self):
        self.app_status_to.stop()
        if self.dlg is not None:
            self.dlg.response(Gtk.ResponseType.CANCEL)

    def connect_close_signal(self, handler):
        return self.window.connect('delete-event', handler)

    def init_folders_controls(self):
        self.folders = self.builder.get_object('folders_list')
        self.folders_menu = self.builder.get_object('folders_menu')
        self.folders_menu_openfolder = self.builder.get_object(
            'folder_menu_openfolder')
        self.folders_menu_openarchive = self.builder.get_object(
            'folder_menu_openarchive')
        self.folders_menu_editsyncignore = self.builder.get_object(
            'folder_menu_editsyncignore')
        self.folders_selection = self.builder.get_object('folders_selection')
        self.folders_treeview = self.builder.get_object('folders_tree_view')
        self.folders_activity_label = self.builder.get_object(
            'folders_activity_label')
        self.folders_add = self.builder.get_object('folders_add')
        self.folders_remove = self.builder.get_object('folders_remove')
        self.folders_remove.set_sensitive(False)
        self.set_treeview_column_widths(
            self.folders_treeview, self.agent.get_pref('folders_columns',
                                                       [300]))
        self.set_treeview_sort_info(
            self.folders_treeview,
            self.agent.get_pref('folders_sortinfo',
                                [0, Gtk.SortType.ASCENDING]))

    def init_folders_values(self):
        try:
            self.lock()
            folders = self.agent.get_folders()
            if folders is not None:
                for index, value in enumerate(folders):
                    # see in update_folder_values the insane explanation why
                    # also an md5 digest has to be saved
                    digest = md5.new(
                        value['dir'].encode('latin-1')).hexdigest()
                    self.folders.append([
                        self.agent.fix_decode(value['dir']),  # 0:Folder
                        self.get_folder_info_string(value),  # 1:Content
                        value['secret'],  # 2:Secret
                        digest,  # 3:FolderTag
                        Pango.EllipsizeMode.END  # 4:EllipsizeMode
                    ])
                    self.add_device_infos(value, digest)
            self.unlock()
            self.app_status_to.start()
        except requests.exceptions.ConnectionError:
            self.unlock()
            self.onConnectionError()
        except requests.exceptions.HTTPError:
            self.unlock()
            self.onCommunicationError()

    def init_devices_controls(self):
        self.devices = self.builder.get_object('devices_list')
        self.devices_treeview = self.builder.get_object('devices_tree_view')
        self.devices_activity_label = self.builder.get_object(
            'devices_activity_label')
        self.set_treeview_column_widths(
            self.devices_treeview,
            self.agent.get_pref('devices_columns', [150, 300]))
        self.set_treeview_sort_info(
            self.devices_treeview,
            self.agent.get_pref('devices_sortinfo',
                                [0, Gtk.SortType.ASCENDING]))

    def init_transfers_controls(self):
        self.transfers = self.builder.get_object('transfers_list')
        self.transfers_treeview = self.builder.get_object(
            'transfers_tree_view')
        self.transfers_activity_label = self.builder.get_object(
            'transfers_activity_label')
        # TODO: remove placeholder as soon as the suitable API call permits
        #       a working implementation...
        self.transfers.append([
            _('Cannot implement due to missing API'),  # 0:
            _('BitTorrent Inc.'),  # 1:
            '',  # 2:
            '',  # 3:
            Pango.EllipsizeMode.END  # 4:EllipsizeMode
        ])
        self.set_treeview_column_widths(
            self.transfers_treeview,
            self.agent.get_pref('transfers_columns', [300, 150, 80]))

    def init_history_controls(self):
        self.history = self.builder.get_object('history_list')
        self.history_treeview = self.builder.get_object('history_tree_view')
        self.history_activity_label = self.builder.get_object(
            'history_activity_label')
        # TODO: remove placeholder as soon as the suitable API call permits
        #       a working implementation...
        self.history.append([
            _('Now'),  # 0:
            _('Cannot implement due to missing API'),  # 1:
            Pango.EllipsizeMode.END  # 4:EllipsizeMode
        ])
        self.set_treeview_column_widths(
            self.history_treeview, self.agent.get_pref('history_columns',
                                                       [150]))

    def refresh_app_status(self):
        try:
            self.lock()
            folders = self.agent.get_folders()
            # forward scan updates existing folders and adds new ones
            for index, value in enumerate(folders):
                # see in update_folder_values the insane explanation why
                # also an md5 digest has to be saved
                digest = md5.new(value['dir'].encode('latin-1')).hexdigest()
                if not self.update_folder_values(value):
                    # it must be new (probably added via web interface) - let's add it
                    self.folders.append([
                        self.agent.fix_decode(value['dir']),  # 0:Folder
                        self.get_folder_info_string(value),  # 1:Content
                        value['secret'],  # 2:Secret
                        digest,  # 3:FolderTag
                        Pango.EllipsizeMode.END  # 4:EllipsizeMode
                    ])
                self.update_device_infos(value, digest)
            # reverse scan deletes disappeared folders...
            for row in self.folders:
                if not self.folder_exists(folders, row):
                    self.folders.remove(row.iter)
                    self.remove_device_infos(row[2], row[3])
            # update transfer status
            self.update_transfer_status(self.agent.get_speed())
            # TODO: fill file list...
            #       but there is still no suitable API call...
            self.unlock()
            return True
        except requests.exceptions.ConnectionError:
            self.unlock()
            return self.onConnectionError()
        except requests.exceptions.HTTPError:
            self.unlock()
            return self.onCommunicationError()

    def init_transfer_status(self):
        self.update_transfer_status({'upload': 0, 'download': 0})

    def update_transfer_status(self, speed):
        activity = _('{0:.1f} kB/s up, {1:.1f} kB/s down').format(
            speed['upload'] / 1000, speed['download'] / 1000)
        self.folders_activity_label.set_label(activity)
        self.devices_activity_label.set_label(activity)
        self.transfers_activity_label.set_label(activity)
        self.history_activity_label.set_label(activity)

    def update_folder_values(self, value):
        for row in self.folders:
            if value['secret'] == row[2]:
                # found - update information
                row[1] = self.get_folder_info_string(value)
                return True
            elif md5.new(value['dir'].encode('latin-1')).hexdigest() == row[3]:
                # comparing the md5 digests avoids casting errors due to the
                # insane encoding fix tecnique
                # found - secret was changed
                row[1] = self.get_folder_info_string(value)
                row[2] = value['secret']
                return True
        # not found
        return False

    def folder_exists(self, folders, row):
        if folders is not None:
            for index, value in enumerate(folders):
                if value['secret'] == row[2]:
                    return True
                elif md5.new(
                        value['dir'].encode('latin-1')).hexdigest() == row[3]:
                    # comparing the md5 digests avoids casting errors due to the
                    # insane encoding fix tecnique
                    return True
        return False

    def add_device_infos(self, folder, digest):
        foldername = self.agent.fix_decode(folder['dir'])
        peers = self.agent.get_folder_peers(folder['secret'])
        for index, value in enumerate(peers):
            self.devices.append([
                self.agent.fix_decode(value['name']),  # 0:Device
                foldername,  # 1:Folder
                self.get_device_info_string(value),  # 2:Status
                folder['secret'],  # 3:Secret
                digest,  # 4:FolderTag
                value['id'],  # 5:DeviceTag
                self.get_device_info_icon_name(value),  # 6:ConnectionIconName
                Pango.EllipsizeMode.END  # 7:EllipsizeMode
            ])

    def update_device_infos(self, folder, digest):
        foldername = self.agent.fix_decode(folder['dir'])
        peers = self.agent.get_folder_peers(folder['secret'])
        # forward scan updates existing and adds new
        for index, value in enumerate(peers):
            if not self.update_device_values(folder, value, digest):
                # it must be new - let's add it
                self.devices.append([
                    self.agent.fix_decode(value['name']),  # 0:Device
                    foldername,  # 1:Folder
                    self.get_device_info_string(value),  # 2:Status
                    folder['secret'],  # 3:Secret
                    digest,  # 4:FolderTag
                    value['id'],  # 5:DeviceTag
                    self.get_device_info_icon_name(
                        value),  # 6:ConnectionIconName
                    Pango.EllipsizeMode.END  # 7:EllipsizeMode
                ])
        # reverse scan deletes disappeared folders...
        for row in self.devices:
            if row[3] == folder['secret'] or row[4] == digest:
                # it's our folder
                if not self.device_exists(peers, row):
                    self.devices.remove(row.iter)

    def update_device_values(self, folder, peer, digest):
        for row in self.devices:
            if peer['id'] == row[5] and folder['secret'] == row[3]:
                # found - update information
                row[0] = self.agent.fix_decode(peer['name'])
                row[2] = self.get_device_info_string(peer)
                row[6] = self.get_device_info_icon_name(peer)
                return True
            elif peer['id'] == row[5] and digest == row[4]:
                # found - secret probably changed...
                row[0] = self.agent.fix_decode(peer['name'])
                row[2] = self.get_device_info_string(peer)
                row[3] = folder['secret']
                row[6] = self.get_device_info_icon_name(peer)
                return True
        # not found
        return False

    def remove_device_infos(self, secret, digest=None):
        for row in self.devices:
            if secret == row[3]:
                self.devices.remove(row.iter)
            elif digest is not None and digest == row[4]:
                self.devices.remove(row.iter)

    def device_exists(self, peers, row):
        for index, value in enumerate(peers):
            if value['id'] == row[5]:
                return True
        return False

    def get_folder_info_string(self, folder):
        if folder['error'] == 0:
            if folder['indexing'] == 0:
                return _('{0} in {1} files').format(
                    self.sizeof_fmt(folder['size']), str(folder['files']))
            else:
                return _('{0} in {1} files (indexing...)').format(
                    self.sizeof_fmt(folder['size']), str(folder['files']))
        else:
            return self.agent.get_error_message(folder)

    def get_device_info_icon_name(self, peer):
        return {
            'direct': 'btsync-gui-direct',
            'relay': 'btsync-gui-cloud'
        }.get(peer['connection'], 'btsync-gui-unknown')

    def get_device_info_string(self, peer):
        if peer['synced'] != 0:
            dt = datetime.datetime.fromtimestamp(peer['synced'])
            return _('Synced on {0}').format(dt.strftime("%x %X"))
        elif peer['download'] == 0 and peer['upload'] != 0:
            return _('⇧ {0}').format(self.sizeof_fmt(peer['upload']))
        elif peer['download'] != 0 and peer['upload'] == 0:
            return _('⇩ {0}').format(self.sizeof_fmt(peer['download']))
        elif peer['download'] != 0 and peer['upload'] != 0:
            return _('⇧ {0} - ⇩ {1}').format(self.sizeof_fmt(peer['upload']),
                                             self.sizeof_fmt(peer['download']))
        else:
            return _('Idle...')

    def init_preferences_controls(self):
        self.devname = self.builder.get_object('devname')
        self.autostart = self.builder.get_object('autostart')
        self.listeningport = self.builder.get_object('listeningport')
        self.upnp = self.builder.get_object('upnp')
        self.limitdn = self.builder.get_object('limitdn')
        self.limitdnrate = self.builder.get_object('limitdnrate')
        self.limitup = self.builder.get_object('limitup')
        self.limituprate = self.builder.get_object('limituprate')

    def init_preferences_values(self):
        self.lock()
        self.attach(
            self.devname,
            BtValueDescriptor.new_from('device_name',
                                       self.prefs['device_name']))
        # self.autostart.set_active(self.prefs[""]);
        self.autostart.set_sensitive(False)
        self.attach(
            self.listeningport,
            BtValueDescriptor.new_from('listening_port',
                                       self.prefs['listening_port']))
        self.attach(
            self.upnp,
            BtValueDescriptor.new_from('use_upnp', self.prefs['use_upnp']))
        self.attach(
            self.limitdnrate,
            BtValueDescriptor.new_from('download_limit',
                                       self.prefs['download_limit']))
        self.attach(
            self.limituprate,
            BtValueDescriptor.new_from('upload_limit',
                                       self.prefs['upload_limit']))

        self.limitdn.set_active(self.prefs['download_limit'] > 0)
        self.limitup.set_active(self.prefs['upload_limit'] > 0)
        self.unlock()

    def get_treeview_column_widths(self, treewidget):
        columns = treewidget.get_columns()
        widths = []
        for index, value in enumerate(columns):
            widths.append(value.get_width())
        return widths

    def set_treeview_column_widths(self, treewidget, widths):
        columns = treewidget.get_columns()
        for index, value in enumerate(columns):
            if index < len(widths):
                value.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
                value.set_fixed_width(max(widths[index], 32))

    def get_treeview_sort_info(self, treewidget):
        treemodel = treewidget.get_model()
        column_id, sort_order = treemodel.get_sort_column_id()
        return [column_id, int(sort_order)]

    def set_treeview_sort_info(self, treewidget, sortinfo):
        if sortinfo[0] is not None:
            treemodel = treewidget.get_model()
            treemodel.set_sort_column_id(sortinfo[0], sortinfo[1])
            columns = treewidget.get_columns()
            for index, value in enumerate(columns):
                if value.get_sort_column_id() == sortinfo[0]:
                    value.set_sort_order(sortinfo[1])
                    value.set_sort_indicator(True)
                    return

    def onDelete(self, *args):
        width, height = self.window.get_size()
        self.agent.set_pref('windowsize', (width, height))
        self.agent.set_pref(
            'folders_columns',
            self.get_treeview_column_widths(self.folders_treeview))
        self.agent.set_pref(
            'devices_columns',
            self.get_treeview_column_widths(self.devices_treeview))
        self.agent.set_pref(
            'transfers_columns',
            self.get_treeview_column_widths(self.transfers_treeview))
        self.agent.set_pref(
            'history_columns',
            self.get_treeview_column_widths(self.history_treeview))
        self.agent.set_pref('folders_sortinfo',
                            self.get_treeview_sort_info(self.folders_treeview))
        self.agent.set_pref('devices_sortinfo',
                            self.get_treeview_sort_info(self.devices_treeview))
        self.close()

    def onSaveEntry(self, widget, valDesc, newValue):
        try:
            self.agent.set_prefs({valDesc.Name: newValue})
            self.prefs[valDesc.Name] = newValue
        except requests.exceptions.ConnectionError:
            return self.onConnectionError()
        except requests.exceptions.HTTPError:
            return self.onCommunicationError()
        return True

    def onFoldersSelectionChanged(self, selection):
        model, tree_iter = selection.get_selected()
        self.folders_remove.set_sensitive(selection.count_selected_rows() > 0)

    def onFoldersAdd(self, widget):
        self.dlg = BtSyncFolderAdd(self.agent)
        try:
            self.dlg.create()
            result = self.dlg.run()
            if result == Gtk.ResponseType.OK:
                # all checks have already been done. let's go!
                result = self.agent.add_folder(self.dlg.folder,
                                               self.dlg.secret)
                if self.agent.get_error_code(result) == 105:
                    if self.show_question(self.window,
                                          self.agent.get_error_message(
                                              result)) == Gtk.ResponseType.YES:
                        result = self.agent.add_folder(self.dlg.folder,
                                                       self.dlg.secret,
                                                       force=True)
                        if self.agent.get_error_code(result) > 0:
                            self.show_warning(
                                self.window,
                                self.agent.get_error_message(result))
                elif self.agent.get_error_code(result) > 0:
                    self.show_warning(self.window,
                                      self.agent.get_error_message(result))
        except requests.exceptions.ConnectionError:
            pass
        except requests.exceptions.HTTPError:
            pass
        finally:
            self.dlg.destroy()
            self.dlg = None

    def onFoldersRemove(self, widget):
        self.dlg = BtSyncFolderRemove()
        self.dlg.create()
        result = self.dlg.run()
        self.dlg.destroy()
        if result == Gtk.ResponseType.OK:
            model, tree_iter = self.folders_selection.get_selected()
            if tree_iter is not None:
                # ok - let's delete it!
                secret = model[tree_iter][2]
                try:
                    result = self.agent.remove_folder(secret)
                    if self.agent.get_error_code(result) == 0:
                        self.folders.remove(tree_iter)
                        self.remove_device_infos(secret)
                    else:
                        logging.error('Failed to remove folder ' + str(secret))
                except requests.exceptions.ConnectionError:
                    pass
                except requests.exceptions.HTTPError:
                    pass

    def onFoldersMouseClick(self, widget, event):
        x = int(event.x)
        y = int(event.y)
        time = event.time
        pathinfo = widget.get_path_at_pos(x, y)
        if pathinfo is not None:
            if event.button == 1:
                if event.type == Gdk.EventType._2BUTTON_PRESS or event.type == Gdk.EventType._3BUTTON_PRESS:
                    path, column, cellx, celly = pathinfo
                    widget.grab_focus()
                    widget.set_cursor(path, column, 0)
                    model, tree_iter = self.folders_selection.get_selected()
                    if tree_iter is not None:
                        if os.path.isdir(model[tree_iter][0]):
                            os.system('xdg-open "{0}"'.format(
                                model[tree_iter][0]))
                            return True
            elif event.button == 3:
                path, column, cellx, celly = pathinfo
                widget.grab_focus()
                widget.set_cursor(path, column, 0)
                model, tree_iter = self.folders_selection.get_selected()
                if self.agent.is_local() and tree_iter is not None:
                    self.folders_menu_openfolder.set_sensitive(
                        os.path.isdir(model[tree_iter][0]))
                    self.folders_menu_openarchive.set_sensitive(
                        os.path.isdir(model[tree_iter][0] + '/.SyncArchive'))
                    self.folders_menu_editsyncignore.set_sensitive(
                        os.path.isfile(model[tree_iter][0] + '/.SyncIgnore'))
                else:
                    self.folders_menu_openfolder.set_sensitive(False)
                    self.folders_menu_openarchive.set_sensitive(False)
                    self.folders_menu_editsyncignore.set_sensitive(False)

                self.folders_menu.popup(None, None, None, None, event.button,
                                        time)
                return True

    def onFoldersCopySecret(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            self.clipboard.set_text(model[tree_iter][2], -1)

    def onFoldersConnectMobile(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            result = self.agent.get_secrets(model[tree_iter][2], False)
            if self.agent.get_error_code(result) == 0:
                self.dlg = BtSyncFolderScanQR(
                    result['read_write'] if result.has_key('read_write') else
                    None, result['read_only'],
                    os.path.basename(model[tree_iter][0]))
                self.dlg.create()
                result = self.dlg.run()
                self.dlg.destroy()
                self.dlg = None

    def onFoldersOpenFolder(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            if os.path.isdir(model[tree_iter][0]):
                os.system('xdg-open "{0}"'.format(model[tree_iter][0]))

    def onFoldersOpenArchive(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            syncarchive = model[tree_iter][0] + '/.SyncArchive'
            if os.path.isdir(syncarchive):
                os.system('xdg-open "{0}"'.format(syncarchive))

    def onFoldersEditSyncIgnore(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            syncignore = model[tree_iter][0] + '/.SyncIgnore'
            if os.path.isfile(syncignore):
                os.system('xdg-open "{0}"'.format(syncignore))

    def onFoldersPreferences(self, widget):
        model, tree_iter = self.folders_selection.get_selected()
        if tree_iter is not None:
            self.dlg = BtSyncFolderPrefs(self.agent)
            try:
                self.dlg.create(model[tree_iter][0], model[tree_iter][2])
                self.dlg.run()
            except requests.exceptions.ConnectionError:
                pass
            except requests.exceptions.HTTPError:
                pass
            finally:
                self.dlg.destroy()
                self.dlg = None

    def onPreferencesToggledLimitDn(self, widget):
        self.limitdnrate.set_sensitive(widget.get_active())
        if not self.is_locked():
            rate = int(
                self.limitdnrate.get_text()) if widget.get_active() else 0
            try:
                self.agent.set_prefs({"download_limit": rate})
                self.prefs['download_limit'] = rate
            except requests.exceptions.ConnectionError:
                return self.onConnectionError()
            except requests.exceptions.HTTPError:
                return self.onCommunicationError()

    def onPreferencesToggledLimitUp(self, widget):
        self.limituprate.set_sensitive(widget.get_active())
        if not self.is_locked():
            rate = int(
                self.limituprate.get_text()) if widget.get_active() else 0
            try:
                self.agent.set_prefs({"upload_limit": rate})
                self.prefs['upload_limit'] = rate
            except requests.exceptions.ConnectionError:
                return self.onConnectionError()
            except requests.exceptions.HTTPError:
                return self.onCommunicationError()

    def onPreferencesClickedAdvanced(self, widget):
        try:
            self.dlg = BtSyncPrefsAdvanced(self.agent)
            self.dlg.run()
        except requests.exceptions.ConnectionError:
            logging.error('BtSync API Connection Error')
        except requests.exceptions.HTTPError:
            logging.error('BtSync API HTTP error: {0}'.format(
                self.agent.get_status_code()))
        except Exception as e:
            # this should not really happen...
            logging.error(
                'onPreferencesClickedAdvanced: Unexpected exception caught: ' +
                str(e))
        finally:
            if isinstance(self.dlg, BtSyncPrefsAdvanced):
                self.dlg.destroy()
            self.dlg = None

    def onConnectionError(self):
        logging.error('BtSync API Connection Error')
        self.window.destroy()
        return False

    def onCommunicationError(self):
        logging.error('BtSync API HTTP error: {0}'.format(
            self.agent.get_status_code()))
        self.window.destroy()
        return False
예제 #8
0
class BtSyncStatus:
	DISCONNECTED	= 0
	CONNECTING		= 1
	CONNECTED		= 2
	PAUSED			= 3

	def __init__(self,agent):
		self.builder = Gtk.Builder()
		self.builder.set_translation_domain('btsync-gui')
		self.builder.add_from_file(os.path.dirname(__file__) + "/btsyncstatus.glade")
		self.builder.connect_signals (self)
		self.menu = self.builder.get_object('btsyncmenu')
		self.menuconnection = self.builder.get_object('connectionitem')
		self.menustatus = self.builder.get_object('statusitem')
		self.menupause = self.builder.get_object('pausesyncing')
		self.menudebug = self.builder.get_object('setdebug')
		self.menuopenweb = self.builder.get_object('openweb')
		self.menuopenapp = self.builder.get_object('openapp')
		self.about = self.builder.get_object('aboutdialog')


		self.ind = TrayIndicator (
			'btsync',
			'btsync-gui-disconnected'
		)
		if agent.is_auto():
			self.menuconnection.set_visible(False)
			self.ind.set_title(_('BitTorrent Sync'))
			self.ind.set_tooltip_text(_('BitTorrent Sync Status Indicator'))
		else:
			self.menuconnection.set_label('{0}:{1}'.format(agent.get_host(),agent.get_port()))
			self.ind.set_title(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
			self.ind.set_tooltip_text(_('BitTorrent Sync {0}:{1}').format(agent.get_host(),agent.get_port()))
		self.menuopenweb.set_visible(agent.is_webui())
		self.ind.set_menu(self.menu)
		self.ind.set_default_action(self.onActivate)

		# icon animator
		self.frame = 0
		self.rotating = False
		self.transferring = False
		self.animator_id = None

		# application window
		self.app = None

		# other variables
		self.connection = BtSyncStatus.DISCONNECTED
		self.connect_id = None
		self.status_to = BtDynamicTimeout(1000,self.btsync_refresh_status)
		self.agent = agent

	def startup(self):
		self.btsyncver = { 'version': '0.0.0' }
		# status
		if self.agent.is_auto():
			self.menupause.set_sensitive(self.agent.is_auto())
			if self.agent.is_paused():
				self.set_status(BtSyncStatus.PAUSED)
				self.menupause.set_active(True)
			else:
				self.set_status(BtSyncStatus.CONNECTING)
				self.menupause.set_active(False)
				self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		else:
			self.set_status(BtSyncStatus.CONNECTING)
			self.menupause.set_sensitive(False)
			self.menupause.set_active(False)
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		
	def shutdown(self):
		if self.animator_id is not None:
			GObject.source_remove(self.animator_id)
		if self.connect_id is not None:
			GObject.source_remove(self.connect_id)
		self.status_to.stop()

	def open_app(self):
		if isinstance(self.app, BtSyncApp):
			self.app.window.present()
		else:
			try:
				self.app = BtSyncApp(self.agent)
				self.app.connect_close_signal(self.onDeleteApp)
			except requests.exceptions.ConnectionError:
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				return self.onCommunicationError()

	def close_app(self,stillopen=True):
		if isinstance(self.app, BtSyncApp):
			if stillopen:
				self.app.close()
				# self.app.window.close()
				self.app.window.destroy()
			del self.app
			self.app = None

	def btsync_connect(self):
		if self.connection is BtSyncStatus.DISCONNECTED or \
			self.connection is BtSyncStatus.CONNECTING or \
			self.connection is BtSyncStatus.PAUSED:
			try:
				self.set_status(BtSyncStatus.CONNECTING)
				self.menustatus.set_label(_('Connecting...'))
				version = self.agent.get_version()
				self.btsyncver = version
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Idle'))
				self.status_to.start()
				self.connect_id = None
				return False

			except requests.exceptions.ConnectionError:
				self.connect_id = None
				return self.onConnectionError()
			except requests.exceptions.HTTPError:
				self.connect_id = None
				return self.onCommunicationError()


		else:
			logging.info('Cannot connect since I\'m already connected')
		

	def btsync_refresh_status(self):
		if self.connection is not BtSyncStatus.CONNECTED:
			logging.info('Interrupting refresh sequence...')
			return False
		logging.info('Refresh status...')
		indexing = False
		transferring = False
		try:
			folders = self.agent.get_folders()
			for fIndex, fValue in enumerate(folders):
				if fValue['indexing'] > 0:
					indexing = True
# this takes too much resources...
#				peers = self.agent.get_folder_peers(fValue['secret'])
#				for pIndex, pValue in enumerate(peers):
#					if long(pValue['upload']) + long(pValue['download']) > 0:
#						transferring = True
#####
			speed = self.agent.get_speed()
			if transferring or speed['upload'] > 0 or speed['download'] > 0:
				# there are active transfers...
				self.set_status(BtSyncStatus.CONNECTED,True)
				self.menustatus.set_label(_('{0:.1f} kB/s up, {1:.1f} kB/s down').format(speed['upload'] / 1000, speed['download'] / 1000))
			elif indexing:
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Indexing...'))
			else:
				self.set_status(BtSyncStatus.CONNECTED)
				self.menustatus.set_label(_('Idle'))
			return True
	
		except requests.exceptions.ConnectionError:
			return self.onConnectionError()
		except requests.exceptions.HTTPError:
			return self.onCommunicationError()

	def set_status(self,connection,transferring=False):
		if connection is BtSyncStatus.DISCONNECTED:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-disconnected')
			self.menudebug.set_sensitive(False)
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		elif connection is BtSyncStatus.CONNECTING:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-connecting')
			self.menudebug.set_sensitive(False)
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		elif connection is BtSyncStatus.PAUSED:
			self.frame = -1
			self.transferring = False
			self.ind.set_from_icon_name('btsync-gui-paused')
			self.menudebug.set_sensitive(self.agent.is_local())
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(False)
			self.menuopenweb.set_sensitive(False)
		else:
			self.menudebug.set_sensitive(self.agent.is_local())
			self.menudebug.set_active(self.agent.get_debug())
			self.menuopenapp.set_sensitive(True)
			self.menuopenweb.set_sensitive(True)
			if transferring and not self.transferring:
				if not self.rotating:
					# initialize animation
					self.transferring = True
					self.frame = 0
					self.animator_id = GObject.timeout_add(200, self.onIconRotate)
			self.transferring = transferring
			if not self.transferring:
				self.ind.set_from_icon_name('btsync-gui-0')
		self.connection = connection

	def show_status(self,statustext):
		self.menustatus.set_label(statustext)

	def is_connected(self):
		return self.connection is BtSyncStatus.CONNECTED

	def onActivate(self,widget):
#		self.menu.popup(None,None,Gtk.StatusIcon.position_menu,widget,3,0)
		if self.is_connected():
			self.open_app()

	def onAbout(self,widget):
		self.about.set_version(_('Version {0} ({0})').format(self.btsyncver['version']))
		self.about.set_comments(_('Linux UI Version {0}').format(VERSION))
		self.about.show()
		self.about.run()
		self.about.hide()

	def onOpenApp(self,widget):
		self.open_app()

	def onOpenWeb(self,widget):
		webbrowser.open('http://{0}:{1}@{2}:{3}'.format(
			urllib.quote(self.agent.get_username(),''),
			urllib.quote(self.agent.get_password(),''),
			self.agent.get_host(),
			self.agent.get_port()
		), 2)

	def onDeleteApp(self, *args):
		self.close_app(False)

	def onSendFeedback(self,widget):
		webbrowser.open(
			'http://forum.bittorrent.com/topic/28106-linux-desktop-gui-unofficial-packages-for-bittorrent-sync/',
			2
		)

	def onOpenManual(self,widget):
		os.system('xdg-open "/usr/share/doc/btsync-common/BitTorrentSyncUserGuide.pdf.gz"')

	def onTogglePause(self,widget):
		if widget.get_active() and not self.agent.is_paused():
			logging.info('Suspending agent...')
			self.close_app();
			self.set_status(BtSyncStatus.PAUSED)
			self.agent.suspend()
		elif not widget.get_active() and self.agent.is_paused():
			logging.info('Resuming agent...')
			self.set_status(BtSyncStatus.CONNECTING)
			self.agent.resume()
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)

	def onToggleLogging(self,widget):
		if self.is_connected():
			if widget.get_active() and not self.agent.get_debug():
				logging.info('Activate logging...')
				self.agent.set_debug(True)
			elif not widget.get_active() and self.agent.get_debug():
				logging.info('Disable logging...')
				self.agent.set_debug(False)

	def onQuit(self,widget):
		Gtk.main_quit()

	def onIconRotate(self):
		if self.frame == -1:
			# immediate stop
			self.frame = 0
			self.rotating = False
			self.animator_id = None
			return False
		elif not self.transferring and self.frame % 12 == 0:
			# do not stop immediately - wait for the
			# cycle to finish.
			self.ind.set_from_icon_name('btsync-gui-0')
			self.rotating = False
			self.frame = 0
			self.animator_id = None
			return False
		else:
			self.ind.set_from_icon_name('btsync-gui-{0}'.format(self.frame % 12))
			self.rotating = True
			self.frame += 1
			return True

	def onConnectionError(self):
		self.set_status(BtSyncStatus.DISCONNECTED)
		self.menustatus.set_label(_('Disconnected'))
		self.close_app();
		logging.info('BtSync API Connection Error')
		if self.agent.is_auto() and not self.agent.is_running():
			logging.warning('BitTorrent Sync seems to be crashed. Restarting...')
			self.agent.start_agent()
			self.connect_id = GObject.timeout_add(1000, self.btsync_connect)
		else:
			self.connect_id = GObject.timeout_add(5000, self.btsync_connect)
		return False

	def onCommunicationError(self):
		self.set_status(BtSyncStatus.DISCONNECTED)
		self.menustatus.set_label(_('Disconnected: Communication Error {0}').format(self.agent.get_status_code()))
		self.close_app();
		logging.warning('BtSync API HTTP error: {0}'.format(self.agent.get_status_code()))
		self.connect_id = GObject.timeout_add(5000, self.btsync_connect)
		return False