Exemple #1
0
 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()
Exemple #2
0
def appExit():
    """ Exit from application (it closes all APPINDICATORS) """
    # global APPINDICATORS
    LOGGER.debug("Exit started")
    for i in APPINDICATORS:
        i.exit()
    Gtk.main_quit()
Exemple #3
0
    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)
Exemple #4
0
 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)
Exemple #5
0
        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
Exemple #6
0
 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']
Exemple #7
0
    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'])
Exemple #8
0
 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')
Exemple #9
0
        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
Exemple #10
0
 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)
Exemple #11
0
 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)
Exemple #12
0
 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')
Exemple #13
0
 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
Exemple #14
0
 def change(self, vals):
     """ Updates handler """
     LOGGER.debug('%sUpdate event: %s', self.ID, str(vals))
Exemple #15
0
 def error(self, errStr, cfgPath):
     """ Error handler """
     LOGGER.debug('%sError %s , path %s', self.ID, errStr, cfgPath)
     return 0
Exemple #16
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