示例#1
0
    def __init__(self, *args, **kw):
        super(TogglWorkflow, self).__init__(*args, **kw)
        self.cache = JsonFile(os.path.join(self.cache_dir, 'cache.json'),
                              ignore_errors=True)

        self.config.header = CONFIG_HEADER.strip()

        if 'use_notifier' not in self.config:
            self.config['use_notifier'] = False

        if 'api_key' not in self.config:
            self.show_message(
                'First things first...',
                'Before you can use this workflow, you need '
                'to set your Toggl API key. You can find your '
                'key on the My Profile page at Toggl.com')

            answer, key = self.get_from_user('Set an API key', 'Toggl API key')
            if answer == 'Ok':
                self.config['api_key'] = key
                self.show_message('Good to go', 'Your key has been set!')

        toggl.api_key = self.config['api_key']
        if self.config['use_notifier']:
            self.run_script('tell application "TogglNotifier" to '
                            'set api key to "{0}"'.format(
                                self.config['api_key']))
示例#2
0
    def __init__(self, *args, **kw):
        super(TogglWorkflow, self).__init__(*args, **kw)
        self.cache = JsonFile(os.path.join(self.cache_dir, 'cache.json'),
                              ignore_errors=True)

        self.config.header = CONFIG_HEADER.strip()

        if 'use_notifier' not in self.config:
            self.config['use_notifier'] = False

        if 'api_key' not in self.config:
            self.show_message('First things first...',
                              'Before you can use this workflow, you need '
                              'to set your Toggl API key. You can find your '
                              'key on the My Profile page at Toggl.com')

            answer, key = self.get_from_user('Set an API key', 'Toggl API key')
            if answer == 'Ok':
                self.config['api_key'] = key
                self.show_message('Good to go', 'Your key has been set!')

        toggl.api_key = self.config['api_key']
        if self.config['use_notifier']:
            self.run_script('tell application "TogglNotifier" to '
                            'set api key to "{0}"'.format(
                            self.config['api_key']))
示例#3
0
    def _load_settings(self):
        '''Get an the location and units to use'''
        version = self.config.get('version')
        if version is not None and version < SETTINGS_VERSION:
            self._migrate_settings()

        self.config['version'] = SETTINGS_VERSION
        self.config['units'] = self.config.get('units', DEFAULT_UNITS);
        self.config['icons'] = self.config.get('icons', DEFAULT_ICONS);
        self.config['time_format'] = self.config.get('time_format', DEFAULT_TIME_FMT);
        self.config['days'] = self.config.get('days', 3);

        import os.path
        old_config_file = os.path.join(self.data_dir, 'settings.json')
        if (os.path.exists(old_config_file) and not self.config.get('migrated')):
            old_config = JsonFile(old_config_file)
            for key, value in old_config.items():
                self.config[key] = value
            self.config['migrated'] = True
示例#4
0
    def _load_settings(self):
        '''Get an the location and units to use'''
        version = self.config.get('version')
        if version is not None and version < SETTINGS_VERSION:
            self._migrate_settings()

        self.config['version'] = SETTINGS_VERSION
        self.config['units'] = self.config.get('units', DEFAULT_UNITS)
        self.config['icons'] = self.config.get('icons', DEFAULT_ICONS)
        self.config['time_format'] = self.config.get('time_format',
                                                     DEFAULT_TIME_FMT)
        self.config['days'] = self.config.get('days', 3)
        self.config['show_localtime'] = self.config.get('show_localtime', True)

        import os.path
        old_config_file = os.path.join(self.data_dir, 'settings.json')
        if (os.path.exists(old_config_file)
                and not self.config.get('migrated')):
            old_config = JsonFile(old_config_file)
            for key, value in old_config.items():
                self.config[key] = value
            self.config['migrated'] = True
示例#5
0
class TogglWorkflow(Workflow):
    def __init__(self, *args, **kw):
        super(TogglWorkflow, self).__init__(*args, **kw)
        self.cache = JsonFile(os.path.join(self.cache_dir, 'cache.json'),
                              ignore_errors=True)

        self.config.header = CONFIG_HEADER.strip()

        if 'use_notifier' not in self.config:
            self.config['use_notifier'] = False

        if 'api_key' not in self.config:
            self.show_message(
                'First things first...',
                'Before you can use this workflow, you need '
                'to set your Toggl API key. You can find your '
                'key on the My Profile page at Toggl.com')

            answer, key = self.get_from_user('Set an API key', 'Toggl API key')
            if answer == 'Ok':
                self.config['api_key'] = key
                self.show_message('Good to go', 'Your key has been set!')

        toggl.api_key = self.config['api_key']
        if self.config['use_notifier']:
            self.run_script('tell application "TogglNotifier" to '
                            'set api key to "{0}"'.format(
                                self.config['api_key']))

    def tell_query(self, query, start=None, end=None):
        '''List entries that match a query.

        Note that an end time without a start time will be ignored.'''
        LOG.info('tell_query("{0}", start={1}, end={2})'.format(
            query, start, end))
        if not start:
            end = None

        needs_refresh = False
        query = query.strip()

        if self.cache.get('disable_cache', False):
            LOG.debug('cache is disabled')
            needs_refresh = True
        elif self.cache.get('time') and self.cache.get('time_entries'):
            last_load_time = self.cache.get('time')
            LOG.debug('last load was %s', last_load_time)
            import time
            now = int(time.time())
            if now - last_load_time > CACHE_LIFETIME:
                LOG.debug('automatic refresh')
                needs_refresh = True
        else:
            LOG.debug('cache is missing timestamp or data')
            needs_refresh = True

        if needs_refresh:
            LOG.debug('refreshing cache')

            try:
                all_entries = toggl.TimeEntry.all()
            except Exception:
                LOG.exception('Error getting time entries')
                raise Exception('Problem talking to toggl.com')

            import time
            self.cache['time'] = int(time.time())
            self.cache['time_entries'] = serialize_entries(all_entries)
        else:
            LOG.debug('using cached data')
            all_entries = deserialize_entries(self.cache['time_entries'])

        LOG.debug('%d entries', len(all_entries))

        if start:
            LOG.debug('filtering on start time %s', start)
            if end:
                LOG.debug('filtering on end time %s', end)
                all_entries = [
                    e for e in all_entries
                    if e.start_time < end and e.stop_time > start
                ]
            else:
                all_entries = [e for e in all_entries if e.stop_time > start]
            LOG.debug('filtered to %d entries', len(all_entries))

        efforts = {}

        # group entries with the same description into efforts (so as not to be
        # confused with Toggl tasks
        for entry in all_entries:
            if entry.description not in efforts:
                efforts[entry.description] = Effort(entry.description, start,
                                                    end)
            efforts[entry.description].add(entry)

        efforts = efforts.values()
        efforts = sorted(efforts,
                         reverse=True,
                         key=lambda e: e.newest_entry.start_time)

        items = []

        if start:
            if len(efforts) > 0:
                seconds = sum(e.seconds for e in efforts)
                LOG.debug('total seconds: %s', seconds)
                total_time = to_hours_str(seconds)

                if end:
                    item = Item('{0} hours on {1}'.format(
                        total_time,
                        start.date().strftime(DATE_FORMAT)),
                                subtitle=Item.LINE)
                else:
                    item = Item('{0} hours from {1}'.format(
                        total_time,
                        start.date().strftime(DATE_FORMAT)),
                                subtitle=Item.LINE)
            else:
                item = Item('Nothing to report')

            items.append(item)

        show_suffix = start or end

        for effort in efforts:
            item = Item(effort.description, valid=True)
            now = LOCALTZ.localize(datetime.datetime.now())

            newest_entry = effort.newest_entry
            if newest_entry.is_running:
                item.icon = 'running.png'
                started = newest_entry.start_time
                delta = to_approximate_time(now - started)

                seconds = effort.seconds
                total = ''
                if seconds > 0:
                    hours = to_hours_str(seconds, show_suffix=show_suffix)
                    total = ' ({0} hours total)'.format(hours)
                item.subtitle = 'Running for {0}{1}'.format(delta, total)
                item.arg = 'stop|{0}|{1}'.format(newest_entry.id,
                                                 effort.description)
            else:
                seconds = effort.seconds
                hours = to_hours_str(datetime.timedelta(seconds=seconds),
                                     show_suffix=show_suffix)

                if start:
                    item.subtitle = ('{0} hours'.format(hours))
                else:
                    stop = newest_entry.stop_time
                    if stop:
                        delta = to_approximate_time(now - stop, ago=True)
                    else:
                        delta = 'recently'
                    oldest = effort.oldest_entry
                    since = oldest.start_time
                    since = since.strftime('%m/%d')
                    item.subtitle = ('{0} hours since {1}, '
                                     'stopped {2}'.format(hours, since, delta))

                item.arg = 'continue|{0}|{1}'.format(newest_entry.id,
                                                     effort.description)

            items.append(item)

        if len(query.strip()) > 1:
            # there's a filter
            test = query[1:].strip()
            items = self.fuzzy_match_list(test, items, key=lambda t: t.title)

        if len(items) == 0:
            items.append(Item("Nothing found"))

        return items

    def tell_since(self, query):
        '''Return info about entries since a time

        A time may be:
            - a date
            - one of {'today', 'yesterday', 'this week'}
        '''
        query = query.strip()
        if not query:
            return [
                Item('Enter a start time',
                     subtitle='This can be a time, '
                     'date, datetime, "yesterday", "tuesday", ...')
            ]
        return self.tell_query('', start=get_start(query))

    def tell_on(self, query):
        '''Return info about entries over a span

        A span may be:
            - a single start date, which denotes a span from that date to now
            - one of {'today', 'yesterday', 'this week'}
            - a week day name
        '''
        query = query.strip()
        if not query:
            return [
                Item('Enter a date', subtitle='9/8, yesterday, monday, '
                     '...')
            ]
        return self.tell_query('', start=get_start(query), end=get_end(query))

    def tell_start(self, query):
        LOG.info('adding a new time entry...')
        items = []
        desc = query.strip()
        if desc:
            items.append(
                Item('Creating timer "{0}"...'.format(desc),
                     arg='start|' + desc,
                     valid=True))
        else:
            items.append(Item('Waiting for description...'))
        return items

    def tell_help(self, query):
        items = []
        items.append(
            Item("Use '/' to list existing timers",
                 subtitle='Type some text to filter the results'))
        items.append(
            Item("Use '//' to force a cache refresh",
                 subtitle='Data from Toggl is normally cached for '
                 '{0} seconds'.format(CACHE_LIFETIME)))
        items.append(
            Item("Use '<' to list timers started since a time",
                 subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...'))
        items.append(
            Item("Use '@' to list time spent on a particular date",
                 subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...'))
        items.append(
            Item("Use '+' to start a new timer",
                 subtitle="Type a description after the '+'"))
        items.append(
            Item("Use '>' to access other commands",
                 subtitle='Enable menubar icon, go to toggl.com, '
                 '...'))
        items.append(Item("Select an existing timer to toggle it"))
        return items

    def tell_commands(self, query):
        LOG.info('telling cmd with "{0}"'.format(query))
        items = []

        items.append(
            Item('Open toggl.com',
                 arg='open|https://new.toggl.com/app',
                 subtitle='Open a browser tab for toggl.com',
                 valid=True))

        items.append(
            Item('Open the workflow config file',
                 arg='open|' + self.config_file,
                 subtitle='Change workflow options here, like the '
                 'debug log level',
                 valid=True))

        items.append(
            Item('Open the debug log',
                 arg='open|' + self.log_file,
                 subtitle='Open a browser tab for toggl.com',
                 valid=True))

        if self.config['use_notifier']:
            items.append(
                Item('Disable the menubar notifier',
                     subtitle='Exit and disable the menubar notifier',
                     arg='disable_notifier',
                     valid=True))
        else:
            items.append(
                Item('Enable the menubar notifier',
                     subtitle='Start and enable the menubar notifier',
                     arg='enable_notifier',
                     valid=True))

        items.append(
            Item('Clear the cache',
                 subtitle='Force a cache refresh on the next query',
                 arg='force_refresh',
                 valid=True))

        if 'api_key' in self.config:
            items.append(
                Item('Forget your API key',
                     subtitle='Forget your stored API key, allowing '
                     'you to change it',
                     arg='clear_key',
                     valid=True))

        if len(query.strip()) > 1:
            # there's a filter
            items = self.fuzzy_match_list(query.strip(),
                                          items,
                                          key=lambda t: t.title)
        if len(items) == 0:
            items.append(Item("Invalid command"))

        return items

    def do_action(self, query):
        LOG.info('doing action with %s', query)
        cmd, sep, arg = query.partition('|')

        if cmd == 'start':
            entry = toggl.TimeEntry.start(arg)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to set '
                                'active timer to "{0}|{1}"'.format(
                                    entry.id, arg))
            self.puts('Started {0}'.format(arg))

        elif cmd == 'continue':
            tid, sep, desc = arg.partition('|')
            entry = toggl.TimeEntry.start(desc)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to set '
                                'active timer to "{0}|{1}"'.format(
                                    entry.id, desc))
            self.puts('Continued {0}'.format(desc))

        elif cmd == 'stop':
            tid, sep, desc = arg.partition('|')
            toggl.TimeEntry.stop(tid)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to be '
                                'stopped')

            self.puts('Stopped {0}'.format(desc))

        elif cmd == 'enable_notifier':
            self.config['use_notifier'] = True
            self.run_script('tell application "TogglNotifier" to activate')
            self.run_script('tell application "TogglNotifier" to set api key '
                            'to "{0}"'.format(toggl.api_key))
            self.puts('Notifier enabled')

        elif cmd == 'disable_notifier':
            self.config['use_notifier'] = False
            self.run_script('tell application "TogglNotifier" to quit')
            self.puts('Notifier disabled')

        elif cmd == 'clear_key':
            del self.config['api_key']
            self.run_script('tell application "TogglNotifier" to quit')
            self.puts('Cleared API key')

        elif cmd == 'force_refresh':
            self.cache['time_entries'] = None

        elif cmd == 'open':
            from subprocess import call
            call(['open', arg])

        else:
            self.puts('Unknown command "{0}"'.format(cmd))

    def schedule_refresh(self):
        '''Force a refresh next time Toggl is queried'''
        self.cache['time'] = 0
示例#6
0
 def cache(self):
     if not self._cache:
         self._cache = JsonFile(self.cache_file)
     return self._cache
示例#7
0
class TogglWorkflow(Workflow):
    def __init__(self, *args, **kw):
        super(TogglWorkflow, self).__init__(*args, **kw)
        self.cache = JsonFile(os.path.join(self.cache_dir, 'cache.json'),
                              ignore_errors=True)

        self.config.header = CONFIG_HEADER.strip()

        if 'use_notifier' not in self.config:
            self.config['use_notifier'] = False

        if 'api_key' not in self.config:
            self.show_message('First things first...',
                              'Before you can use this workflow, you need '
                              'to set your Toggl API key. You can find your '
                              'key on the My Profile page at Toggl.com')

            answer, key = self.get_from_user('Set an API key', 'Toggl API key')
            if answer == 'Ok':
                self.config['api_key'] = key
                self.show_message('Good to go', 'Your key has been set!')

        toggl.api_key = self.config['api_key']
        if self.config['use_notifier']:
            self.run_script('tell application "TogglNotifier" to '
                            'set api key to "{0}"'.format(
                            self.config['api_key']))

    def tell_query(self, query, start=None, end=None):
        '''List entries that match a query.

        Note that an end time without a start time will be ignored.'''
        LOG.info('tell_query("{0}", start={1}, end={2})'.format(
                 query, start, end))
        if not start:
            end = None

        needs_refresh = False
        query = query.strip()

        if self.cache.get('disable_cache', False):
            LOG.debug('cache is disabled')
            needs_refresh = True
        elif self.cache.get('time') and self.cache.get('time_entries'):
            last_load_time = self.cache.get('time')
            LOG.debug('last load was %s', last_load_time)
            import time
            now = int(time.time())
            if now - last_load_time > CACHE_LIFETIME:
                LOG.debug('automatic refresh')
                needs_refresh = True
        else:
            LOG.debug('cache is missing timestamp or data')
            needs_refresh = True

        if needs_refresh:
            LOG.debug('refreshing cache')

            try:
                all_entries = toggl.TimeEntry.all()
            except Exception:
                LOG.exception('Error getting time entries')
                raise Exception('Problem talking to toggl.com')

            import time
            self.cache['time'] = int(time.time())
            self.cache['time_entries'] = serialize_entries(all_entries)
        else:
            LOG.debug('using cached data')
            all_entries = deserialize_entries(self.cache['time_entries'])

        LOG.debug('%d entries', len(all_entries))

        if start:
            LOG.debug('filtering on start time %s', start)
            if end:
                LOG.debug('filtering on end time %s', end)
                all_entries = [e for e in all_entries if e.start_time < end
                               and e.stop_time > start]
            else:
                all_entries = [e for e in all_entries if e.stop_time > start]
            LOG.debug('filtered to %d entries', len(all_entries))

        efforts = {}

        # group entries with the same description into efforts (so as not to be
        # confused with Toggl tasks
        for entry in all_entries:
            if entry.description not in efforts:
                efforts[entry.description] = Effort(entry.description, start,
                                                    end)
            efforts[entry.description].add(entry)

        efforts = efforts.values()
        efforts = sorted(efforts, reverse=True,
                         key=lambda e: e.newest_entry.start_time)

        items = []

        if start:
            if len(efforts) > 0:
                seconds = sum(e.seconds for e in efforts)
                LOG.debug('total seconds: %s', seconds)
                total_time = to_hours_str(seconds)

                if end:
                    item = Item('{0} hours on {1}'.format(
                                total_time,
                                start.date().strftime(DATE_FORMAT)),
                                subtitle=Item.LINE)
                else:
                    item = Item('{0} hours from {1}'.format(
                                total_time,
                                start.date().strftime(DATE_FORMAT)),
                                subtitle=Item.LINE)
            else:
                item = Item('Nothing to report')

            items.append(item)

        show_suffix = start or end

        for effort in efforts:
            item = Item(effort.description, valid=True)
            now = LOCALTZ.localize(datetime.datetime.now())

            newest_entry = effort.newest_entry
            if newest_entry.is_running:
                item.icon = 'running.png'
                started = newest_entry.start_time
                delta = to_approximate_time(now - started)

                seconds = effort.seconds
                total = ''
                if seconds > 0:
                    hours = to_hours_str(seconds, show_suffix=show_suffix)
                    total = ' ({0} hours total)'.format(hours)
                item.subtitle = 'Running for {0}{1}'.format(delta, total)
                item.arg = 'stop|{0}|{1}'.format(newest_entry.id,
                                                 effort.description)
            else:
                seconds = effort.seconds
                hours = to_hours_str(datetime.timedelta(seconds=seconds),
                                     show_suffix=show_suffix)

                if start:
                    item.subtitle = ('{0} hours'.format(hours))
                else:
                    stop = newest_entry.stop_time
                    if stop:
                        delta = to_approximate_time(now - stop, ago=True)
                    else:
                        delta = 'recently'
                    oldest = effort.oldest_entry
                    since = oldest.start_time
                    since = since.strftime('%m/%d')
                    item.subtitle = ('{0} hours since {1}, '
                                     'stopped {2}'.format(hours, since, delta))

                item.arg = 'continue|{0}|{1}'.format(newest_entry.id,
                                                     effort.description)

            items.append(item)

        if len(query.strip()) > 1:
            # there's a filter
            test = query[1:].strip()
            items = self.fuzzy_match_list(test, items,
                                          key=lambda t: t.title)

        if len(items) == 0:
            items.append(Item("Nothing found"))

        return items

    def tell_since(self, query):
        '''Return info about entries since a time

        A time may be:
            - a date
            - one of {'today', 'yesterday', 'this week'}
        '''
        query = query.strip()
        if not query:
            return [Item('Enter a start time', subtitle='This can be a time, '
                         'date, datetime, "yesterday", "tuesday", ...')]
        return self.tell_query('', start=get_start(query))

    def tell_on(self, query):
        '''Return info about entries over a span

        A span may be:
            - a single start date, which denotes a span from that date to now
            - one of {'today', 'yesterday', 'this week'}
            - a week day name
        '''
        query = query.strip()
        if not query:
            return [Item('Enter a date', subtitle='9/8, yesterday, monday, '
                         '...')]
        return self.tell_query('', start=get_start(query), end=get_end(query))

    def tell_start(self, query):
        LOG.info('adding a new time entry...')
        items = []
        desc = query.strip()
        if desc:
            items.append(Item('Creating timer "{0}"...'.format(desc),
                              arg='start|' + desc, valid=True))
        else:
            items.append(Item('Waiting for description...'))
        return items

    def tell_help(self, query):
        items = []
        items.append(Item("Use '/' to list existing timers",
                          subtitle='Type some text to filter the results'))
        items.append(Item("Use '//' to force a cache refresh",
                          subtitle='Data from Toggl is normally cached for '
                                   '{0} seconds'.format(CACHE_LIFETIME)))
        items.append(Item("Use '<' to list timers started since a time",
                          subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...'))
        items.append(Item("Use '@' to list time spent on a particular date",
                          subtitle='9/2, 9/2/13, 2013-9-2T22:00-04:00, ...'))
        items.append(Item("Use '+' to start a new timer",
                          subtitle="Type a description after the '+'"))
        items.append(Item("Use '>' to access other commands",
                          subtitle='Enable menubar icon, go to toggl.com, '
                                   '...'))
        items.append(Item("Select an existing timer to toggle it"))
        return items

    def tell_commands(self, query):
        LOG.info('telling cmd with "{0}"'.format(query))
        items = []

        items.append(Item('Open toggl.com',
                          arg='open|https://new.toggl.com/app',
                          subtitle='Open a browser tab for toggl.com',
                          valid=True))

        items.append(Item('Open the workflow config file',
                          arg='open|' + self.config_file,
                          subtitle='Change workflow options here, like the '
                          'debug log level', valid=True))

        items.append(Item('Open the debug log', arg='open|' + self.log_file,
                          subtitle='Open a browser tab for toggl.com',
                          valid=True))

        if self.config['use_notifier']:
            items.append(Item('Disable the menubar notifier',
                              subtitle='Exit and disable the menubar notifier',
                              arg='disable_notifier', valid=True))
        else:
            items.append(Item('Enable the menubar notifier',
                              subtitle='Start and enable the menubar notifier',
                              arg='enable_notifier', valid=True))

        items.append(Item('Clear the cache',
                          subtitle='Force a cache refresh on the next query',
                          arg='force_refresh', valid=True))

        if 'api_key' in self.config:
            items.append(Item('Forget your API key',
                              subtitle='Forget your stored API key, allowing '
                              'you to change it', arg='clear_key', valid=True))

        if len(query.strip()) > 1:
            # there's a filter
            items = self.fuzzy_match_list(query.strip(), items,
                                          key=lambda t: t.title)
        if len(items) == 0:
            items.append(Item("Invalid command"))

        return items

    def do_action(self, query):
        LOG.info('doing action with %s', query)
        cmd, sep, arg = query.partition('|')

        if cmd == 'start':
            entry = toggl.TimeEntry.start(arg)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to set '
                                'active timer to "{0}|{1}"'.format(entry.id,
                                                                   arg))
            self.puts('Started {0}'.format(arg))

        elif cmd == 'continue':
            tid, sep, desc = arg.partition('|')
            entry = toggl.TimeEntry.start(desc)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to set '
                                'active timer to "{0}|{1}"'.format(entry.id,
                                                                   desc))
            self.puts('Continued {0}'.format(desc))

        elif cmd == 'stop':
            tid, sep, desc = arg.partition('|')
            toggl.TimeEntry.stop(tid)
            self.schedule_refresh()

            if self.config['use_notifier']:
                self.run_script('tell application "TogglNotifier" to be '
                                'stopped')

            self.puts('Stopped {0}'.format(desc))

        elif cmd == 'enable_notifier':
            self.config['use_notifier'] = True
            self.run_script('tell application "TogglNotifier" to activate')
            self.run_script('tell application "TogglNotifier" to set api key '
                            'to "{0}"'.format(toggl.api_key))
            self.puts('Notifier enabled')

        elif cmd == 'disable_notifier':
            self.config['use_notifier'] = False
            self.run_script('tell application "TogglNotifier" to quit')
            self.puts('Notifier disabled')

        elif cmd == 'clear_key':
            del self.config['api_key']
            self.run_script('tell application "TogglNotifier" to quit')
            self.puts('Cleared API key')

        elif cmd == 'force_refresh':
            self.cache['time_entries'] = None

        elif cmd == 'open':
            from subprocess import call
            call(['open', arg])

        else:
            self.puts('Unknown command "{0}"'.format(cmd))

    def schedule_refresh(self):
        '''Force a refresh next time Toggl is queried'''
        self.cache['time'] = 0