def eventHandler(watch): """ Handles watcher (when watch=False) and and timer (when watch=True) events. After receiving and parsing the daemon output it raises outside change event if daemon changes at least one of its status values. """ # Enter to critical section through acquiring of the lock as it can be called from two different threads self.__lock.acquire() # Parse fresh daemon output. Parsing returns true when something changed if self.__parseOutput(self.__getOutput()): LOGGER.debug('%sEvent raised by %s', self.ID, (' Watcher' if watch else ' Timer')) self.change( self.__v) # Call the callback of update event handler # --- Handle timer delays --- self.__timer.cancel() # Cancel timer if it still active if watch or self.__v['status'] == 'busy': delay = 2 # Initial delay self.__tCnt = 0 # Reset counter else: # It called by timer delay = 2 + self.__tCnt # Increase interval up to 10 sec (2 + 8) self.__tCnt += 1 # Increase counter to increase delay next activation. if self.__tCnt < 9: # Don't start timer after 10 seconds delay self.__timer = thTimer(delay, eventHandler, (False, )) self.__timer.start() # Leave the critical section self.__lock.release()
def appExit(): """ Exit from application (it closes all APPINDICATORS) """ # global APPINDICATORS LOGGER.debug("Exit started") for i in APPINDICATORS: i.exit() Gtk.main_quit()
def error(self, errStr, cfgPath): """ Error handler GUI implementation """ # it must handle two types of error cases: # - yandex-disk is not installed (errStr=='' in that case) - just show error message and return # - yandex-disk is not configured (errStr!='' in that case) - suggest to configure it and run ya-setup if needed if errStr == '': text1 = _('Yandex.Disk Indicator: daemon start failed') buttons = Gtk.ButtonsType.OK text2 = (_( 'Yandex.Disk utility is not installed.\n' + 'Visit www.yandex.ru, download and install Yandex.Disk daemon.' )) else: text1 = _('Yandex.Disk Indicator: daemon start failed') buttons = Gtk.ButtonsType.OK_CANCEL text2 = (_( 'Yandex.Disk daemon failed to start because it is not' + ' configured properly\n\n' + errStr + '\n\n' + ' To configure it up: press OK button.\n Press Cancel to exit.' )) dialog = Gtk.MessageDialog(None, 0, Gtk.MessageType.INFO, buttons, text1) dialog.format_secondary_text(text2) dialog.set_icon(APPLOGO) response = dialog.run() if errStr != '' and response == Gtk.ResponseType.OK: # Launch Set-up utility LOGGER.debug('starting configuration utility') retCode = call([pathJoin(APPINSTPATH, 'ya-setup'), cfgPath]) else: retCode = 1 dialog.destroy() return retCode # 0 when error is not critical or fixed (daemon has been configured via ya-setup)
def openPath(self, _, path): # Open path LOGGER.info("Opening '%s'", path) if pathExists(path): try: call(['xdg-open', path]) except: LOGGER.error("Start of '%s' failed", path)
def update(self, vals, yddir): # Update information in menu self.folder = yddir # Update status data on first run or when status has changed if vals['statchg'] or vals['laststatus'] == 'unknown': self.status.set_label( _('Status: ') + self.YD_STATUS[vals['status']] + (vals['progress'] if vals['status'] == 'busy' else ' '. join((':', vals['error'], shortPath(vals['path']) )) if vals['status'] == 'error' else '')) # Update pseudo-static items on first run or when daemon has stopped or started if 'none' in (vals['status'], vals['laststatus'] ) or vals['laststatus'] == 'unknown': started = vals['status'] != 'none' self.status.set_sensitive(started) # zero-space UTF symbols are used to detect requered action without need to compare translated strings self.daemon_ss.set_label(( '\u2060' + _('Stop Yandex.Disk daemon')) if started else ( '\u200B' + _('Start Yandex.Disk daemon'))) if self.ID != '': # Set daemon identity row in multidaemon mode self.yddir.set_label(self.ID + _(' Folder: ') + (shortPath(yddir) if yddir else '< NOT CONFIGURED >')) self.open_folder.set_sensitive( yddir != '') # Activate Open YDfolder if daemon configured # Update sizes data on first run or when size data has changed if vals['szchg'] or vals['laststatus'] == 'unknown': self.used.set_label( _('Used: ') + vals['used'] + '/' + vals['total']) self.free.set_label( _('Free: ') + vals['free'] + _(', trash: ') + vals['trash']) # Update last synchronized sub-menu on first run or when last data has changed if vals['lastchg'] or vals['laststatus'] == 'unknown': # Update last synchronized sub-menu self.lastItems.destroy( ) # Disable showing synchronized sub menu while updating it - temp fix for #197 self.lastItems = Gtk.Menu() # Create new/empty Sub-menu: for filePath in vals['lastitems']: # Create new sub-menu items # Create menu label as file path (shorten it down to 50 symbols when path length > 50 # symbols), with replaced underscore (to disable menu acceleration feature of GTK menu). widget = Gtk.MenuItem.new_with_label(shortPath(filePath)) filePath = pathJoin(yddir, filePath) # Make full path to file if pathExists(filePath): widget.set_sensitive( True) # If it exists then it can be opened widget.connect("activate", self.openPath, filePath) else: widget.set_sensitive( False) # Don't allow to open non-existing path self.lastItems.append(widget) self.last.set_submenu(self.lastItems) # Switch off last items menu sensitivity if no items in list self.last.set_sensitive(vals['lastitems']) LOGGER.debug("Sub-menu 'Last synchronized' has %s items", str(len(vals['lastitems']))) self.show_all() # Renew menu
def do_change(vals, path): """ Update information in menu """ self.menu.update(vals, path) # Handle daemon status change by icon change if vals['status'] != vals['laststatus']: LOGGER.info('Status: %s -> %s', vals['laststatus'], vals['status']) self.updateIcon(vals['status']) # Update icon # Create notifications for status change events if APPCONF['notifications']: if vals['laststatus'] == 'none': # Daemon has been started self.notify.send( _('Yandex.Disk daemon has been started')) if vals['status'] == 'busy': # Just entered into 'busy' self.notify.send(_('Synchronization started')) elif vals['status'] == 'idle': # Just entered into 'idle' if vals['laststatus'] == 'busy': # ...from 'busy' status self.notify.send( _('Synchronization has been completed')) elif vals[ 'status'] == 'paused': # Just entered into 'paused' if vals['laststatus'] not in [ 'none', 'unknown' ]: # ...not from 'none'/'unknown' status self.notify.send( _('Synchronization has been paused')) elif vals[ 'status'] == 'none': # Just entered into 'none' from some another status if vals['laststatus'] != 'unknown': # ... not from 'unknown' self.notify.send( _('Yandex.Disk daemon has been stopped')) else: # status is 'error' or 'no-net' self.notify.send(_('Synchronization ERROR')) # Remember current status (required for Preferences dialog) self.currentStatus = vals['status']
def change(self, vals): """ Implementation of daemon class call-back function NOTE: it is called not from main thread, so it have to add action in main GUI loop queue It handles daemon status changes by updating icon, creating messages and also update status information in menu (status, sizes and list of last synchronized items). It is called when daemon detects any change of its status. """ LOGGER.info( '%sChange event: %s', self.ID, ','.join([ 'stat' if vals['statchg'] else '', 'size' if vals['szchg'] else '', 'last' if vals['lastchg'] else '' ])) def do_change(vals, path): """ Update information in menu """ self.menu.update(vals, path) # Handle daemon status change by icon change if vals['status'] != vals['laststatus']: LOGGER.info('Status: %s -> %s', vals['laststatus'], vals['status']) self.updateIcon(vals['status']) # Update icon # Create notifications for status change events if APPCONF['notifications']: if vals['laststatus'] == 'none': # Daemon has been started self.notify.send( _('Yandex.Disk daemon has been started')) if vals['status'] == 'busy': # Just entered into 'busy' self.notify.send(_('Synchronization started')) elif vals['status'] == 'idle': # Just entered into 'idle' if vals['laststatus'] == 'busy': # ...from 'busy' status self.notify.send( _('Synchronization has been completed')) elif vals[ 'status'] == 'paused': # Just entered into 'paused' if vals['laststatus'] not in [ 'none', 'unknown' ]: # ...not from 'none'/'unknown' status self.notify.send( _('Synchronization has been paused')) elif vals[ 'status'] == 'none': # Just entered into 'none' from some another status if vals['laststatus'] != 'unknown': # ... not from 'unknown' self.notify.send( _('Yandex.Disk daemon has been stopped')) else: # status is 'error' or 'no-net' self.notify.send(_('Synchronization ERROR')) # Remember current status (required for Preferences dialog) self.currentStatus = vals['status'] idle_add(do_change, vals, self.config['dir'])
def send(self, messg): # global APPLOGO LOGGER.debug('Message: %s | %s', self.title, messg) if self.note is not None: try: self.note.close() except: pass self.note = None try: # Create notification self.note = Notify.Notification.new(self.title, messg) self.note.set_image_from_pixbuf(APPLOGO) self.note.show() # Display new notification except: LOGGER.error('Message engine failure')
def start(self): # Activate iNotify watching if self.status: return if not pathExists(self.path): LOGGER.info( "Watcher was not started: path '%s' was not found.", self.path) return self.mark = stat(self.path).st_ctime_ns def wHandler(): st = stat(self.path).st_ctime_ns if st != self.mark: self.mark = st self.handler(*self.args, **self.kwargs) self.timer = thTimer(0.5, wHandler) self.timer.start() self.timer = thTimer(0.5, wHandler) self.timer.start() self.status = True
def exit(self): # Handle daemon/indicator closing LOGGER.debug("Indicator %sexit started: ", self.ID) self.__watcher.stop() self.__timer.cancel() # stop event timer if it is running # Stop yandex-disk daemon if it is required by its configuration if self.config.get('stoponexitfromindicator', False): self.stop(wait=True) LOGGER.info('Demon %sstopped', self.ID) LOGGER.debug('Indicator %sexited', self.ID)
def onButtonToggled(self, _, button, key, dconfig=None, ow=None): """ Handle clicks on controls """ toggleState = button.get_active() LOGGER.debug('Togged: %s val: %s', key, str(toggleState)) # Update configurations if key in [ 'read-only', 'overwrite', 'startonstartofindicator', 'stoponexitfromindicator' ]: dconfig[key] = toggleState # Update daemon config dconfig.changed = True else: APPCONF.changed = True # Update application config APPCONF[key] = toggleState if key == 'theme': for i in APPINDICATORS: # Update all APPINDICATORS' icons i.setIconTheme(toggleState) # Update icon theme i.updateIcon(i.currentStatus) # Update current icon elif key == 'autostart': if toggleState: copyFile(APPAUTOSTARTSRC, APPAUTOSTARTDST) else: deleteFile(APPAUTOSTARTDST) elif key == 'fmextensions': if not button.get_inconsistent(): # It is a first call if not activateActions(toggleState, APPINSTPATH): toggleState = not toggleState # When activation/deactivation is not success: revert settings back button.set_inconsistent( True) # set inconsistent state to detect second call button.set_active( toggleState) # set check-button to reverted status # set_active will raise again the 'toggled' event else: # This is a second call button.set_inconsistent( False) # Just remove inconsistent status elif key == 'read-only': ow.set_sensitive(toggleState)
def do_stop(): if self.__getOutput() == "": LOGGER.info('Daemon is not started') return try: msg = check_output( [self.__YDC, '-c', self.config.fileName, 'stop'], universal_newlines=True) LOGGER.info('Daemon stopped, message: %s', msg) except: LOGGER.info('Daemon stop failed')
def do_start(): if self.__getOutput() != "": LOGGER.info('Daemon is already started') self.__watcher.start() # Activate file watcher return try: # Try to start msg = check_output( [self.__YDC, '-c', self.config.fileName, 'start'], universal_newlines=True) LOGGER.info('Daemon started, message: %s', msg) except CalledProcessError as e: LOGGER.error('Daemon start failed: %s', e.output) return self.__watcher.start() # Activate file watcher
def change(self, vals): """ Updates handler """ LOGGER.debug('%sUpdate event: %s', self.ID, str(vals))
def error(self, errStr, cfgPath): """ Error handler """ LOGGER.debug('%sError %s , path %s', self.ID, errStr, cfgPath) return 0
# Get command line arguments or their default values args = argParse(APPVER) # Change the process name setProcName(APPHOME) # Check for already running instance of the indicator application # Get PIDs of all runnig processes (of current user) with name 'yd-tools' and compare it with current process PID if str(getpid()) != check_output( ["pgrep", '-u', str(geteuid()), APPHOME], universal_newlines=True).strip(): sysExit(_('The indicator instance is already running.')) # Set user specified logging level LOGGER.setLevel(args.level) # Report app version and logging level LOGGER.info('%s v.%s', APPNAME, APPVER) LOGGER.debug('Logging level: %s', str(args.level)) # Application configuration """ User configuration is stored in ~/.config/<APPHOME>/<APPNAME>.conf file. This file can contain comments (line starts with '#') and config values in form: key=value[,value[,value ...]] where keys and values can be quoted ("...") or not. The following key words are reserved for configuration: autostart, notifications, theme, fmextensions and daemons. The dictionary 'config' stores the config settings for usage in code. Its values are saved to config file on exit from the Menu.Preferences dialogue or when there is no configuration file