support for offline time logging

This commit is contained in:
Alan Hamlett 2014-05-25 17:14:38 -07:00
parent 5eb2ad4bea
commit 22766bb14b
2 changed files with 137 additions and 8 deletions

View file

@ -39,6 +39,7 @@ except ImportError:
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages')) sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), 'packages'))
from .queue import Queue
from .log import setup_logging from .log import setup_logging
from .project import find_project from .project import find_project
from .stats import get_file_stats from .stats import get_file_stats
@ -153,6 +154,9 @@ def parseArguments(argv):
parser.add_argument('--key', dest='key', parser.add_argument('--key', dest='key',
help='your wakatime api key; uses api_key from '+ help='your wakatime api key; uses api_key from '+
'~/.wakatime.conf by default') '~/.wakatime.conf by default')
parser.add_argument('--disableoffline', dest='offline',
action='store_false',
help='disables offline time logging instead of queuing logged time')
parser.add_argument('--ignore', dest='ignore', action='append', parser.add_argument('--ignore', dest='ignore', action='append',
help='filename patterns to ignore; POSIX regex syntax; can be used more than once') help='filename patterns to ignore; POSIX regex syntax; can be used more than once')
parser.add_argument('--logfile', dest='logfile', parser.add_argument('--logfile', dest='logfile',
@ -193,6 +197,8 @@ def parseArguments(argv):
args.ignore.append(pattern) args.ignore.append(pattern)
except TypeError: except TypeError:
pass pass
if args.offline and configs.has_option('settings', 'offline'):
args.offline = configs.getboolean('settings', 'offline')
if not args.verbose and configs.has_option('settings', 'verbose'): if not args.verbose and configs.has_option('settings', 'verbose'):
args.verbose = configs.getboolean('settings', 'verbose') args.verbose = configs.getboolean('settings', 'verbose')
if not args.verbose and configs.has_option('settings', 'debug'): if not args.verbose and configs.has_option('settings', 'debug'):
@ -227,8 +233,8 @@ def get_user_agent(plugin):
return user_agent return user_agent
def send_action(project=None, branch=None, stats={}, key=None, targetFile=None, def send_action(project=None, branch=None, stats=None, key=None, targetFile=None,
timestamp=None, isWrite=None, plugin=None, **kwargs): timestamp=None, isWrite=None, plugin=None, offline=None, **kwargs):
url = 'https://wakatime.com/api/v1/actions' url = 'https://wakatime.com/api/v1/actions'
log.debug('Sending action to api at %s' % url) log.debug('Sending action to api at %s' % url)
data = { data = {
@ -270,24 +276,45 @@ def send_action(project=None, branch=None, stats={}, key=None, targetFile=None,
} }
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc() exception_data['traceback'] = traceback.format_exc()
log.error(exception_data) if offline:
queue = Queue()
queue.push(data, plugin)
if log.isEnabledFor(logging.DEBUG):
log.warn(exception_data)
else:
log.error(exception_data)
except: except:
exception_data = { exception_data = {
sys.exc_info()[0].__name__: str(sys.exc_info()[1]), sys.exc_info()[0].__name__: str(sys.exc_info()[1]),
} }
if log.isEnabledFor(logging.DEBUG): if log.isEnabledFor(logging.DEBUG):
exception_data['traceback'] = traceback.format_exc() exception_data['traceback'] = traceback.format_exc()
log.error(exception_data) if offline:
queue = Queue()
queue.push(data, plugin)
if log.isEnabledFor(logging.DEBUG):
log.warn(exception_data)
else:
log.error(exception_data)
else: else:
if response.getcode() == 201: if response.getcode() == 201:
log.debug({ log.debug({
'response_code': response.getcode(), 'response_code': response.getcode(),
}) })
return True return True
log.error({ if offline:
'response_code': response.getcode(), queue = Queue()
'response_content': response.read(), queue.push(data, plugin)
}) if log.isEnabledFor(logging.DEBUG):
log.warn({
'response_code': response.getcode(),
'response_content': response.read(),
})
else:
log.error({
'response_code': response.getcode(),
'response_content': response.read(),
})
return False return False
@ -323,6 +350,15 @@ def main(argv=None):
stats=stats, stats=stats,
**vars(args) **vars(args)
): ):
queue = Queue()
while True:
action = queue.pop()
if action is None:
break
if not send_action(project=action['project'], targetFile=action['file'], timestamp=action['time'],
branch=action['branch'], stats={'language': action['language'], 'lines': action['lines']},
key=args.key, isWrite=action['is_write'], plugin=action['plugin'], offline=args.offline):
break
return 0 # success return 0 # success
return 102 # api error return 102 # api error

93
wakatime/queue.py Normal file
View file

@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
wakatime.queue
~~~~~~~~~~~~~~
Queue for offline time logging.
http://wakatime.com
:copyright: (c) 2014 Alan Hamlett.
:license: BSD, see LICENSE for more details.
"""
import logging
import os
import sqlite3
from time import sleep
log = logging.getLogger(__name__)
class Queue(object):
DB_FILE = os.path.join(os.path.expanduser('~'), '.wakatime.db')
def connect(self):
exists = os.path.exists(self.DB_FILE)
conn = sqlite3.connect(self.DB_FILE)
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS action (
file text,
time real,
project text,
language text,
lines integer,
branch text,
is_write integer,
plugin text)
''')
return (conn, c)
def push(self, data, plugin):
conn, c = self.connect()
action = {
'file': data.get('file'),
'time': data.get('time'),
'project': data.get('project'),
'language': data.get('language'),
'lines': data.get('lines'),
'branch': data.get('branch'),
'is_write': 1 if data.get('is_write') else 0,
'plugin': plugin,
}
c.execute('INSERT INTO action VALUES (:file,:time,:project,:language,:lines,:branch,:is_write,:plugin)', action)
conn.commit()
conn.close()
def pop(self):
tries = 3
wait = 0.1
action = None
conn, c = self.connect()
loop = True
while loop and tries > -1:
try:
c.execute('BEGIN IMMEDIATE')
c.execute('SELECT * FROM action LIMIT 1')
row = c.fetchone()
if row is not None:
c.execute('''DELETE FROM action WHERE
file=? AND time=? AND project=? AND language=? AND
lines=? AND branch=? AND is_write=?''', row[0:7])
conn.commit()
if row is not None:
action = {
'file': row[0],
'time': row[1],
'project': row[2],
'language': row[3],
'lines': row[4],
'branch': row[5],
'is_write': True if row[6] is 1 else False,
'plugin': row[7],
}
loop = False
except sqlite3.Error, e:
log.debug(str(e))
sleep(wait)
tries -= 1
conn.close()
return action