Upgraded wakatime package to v0.3.0.

Importing wakatime package directly without need for subprocess.
Using threading to prevent blocking Sublime Text.
This commit is contained in:
Alan Hamlett 2013-08-08 02:17:47 -07:00
parent f0e72b10f4
commit b89356b3e7
5 changed files with 102 additions and 143 deletions

View file

@ -13,209 +13,156 @@ import sublime_plugin
import glob import glob
import os import os
import platform import platform
import sys
import time import time
import threading
import uuid import uuid
from os.path import expanduser, dirname, realpath, isfile, join, exists from os.path import expanduser, dirname, realpath, isfile, join, exists
from subprocess import call, Popen
# globals # globals
AWAY_MINUTES = 10
ACTION_FREQUENCY = 5 ACTION_FREQUENCY = 5
ST_VERSION = int(sublime.version())
PLUGIN_DIR = dirname(realpath(__file__)) PLUGIN_DIR = dirname(realpath(__file__))
API_CLIENT = '%s/packages/wakatime/wakatime-cli.py' % PLUGIN_DIR API_CLIENT = '%s/packages/wakatime/wakatime-cli.py' % PLUGIN_DIR
SETTINGS = 'WakaTime.sublime-settings' SETTINGS_FILE = 'WakaTime.sublime-settings'
SETTINGS = {}
LAST_ACTION = 0 LAST_ACTION = 0
LAST_USAGE = 0
LAST_FILE = None LAST_FILE = None
BUSY = False BUSY = False
sys.path.insert(0, join(PLUGIN_DIR, 'packages', 'wakatime'))
import wakatime
def setup_settings_file(): def setup_settings_file():
""" Convert ~/.wakatime.conf to WakaTime.sublime-settings """ Convert ~/.wakatime.conf to WakaTime.sublime-settings
""" """
global SETTINGS
# To be backwards compatible, rename config file # To be backwards compatible, rename config file
settings = sublime.load_settings(SETTINGS) SETTINGS = sublime.load_settings(SETTINGS_FILE)
api_key = settings.get('api_key', '') api_key = SETTINGS.get('api_key', '')
if not api_key: if not api_key:
api_key = ''
try: try:
with open(join(expanduser('~'), '.wakatime.conf')) as old_file: with open(join(expanduser('~'), '.wakatime.conf')) as old_file:
for line in old_file: for line in old_file:
line = line.split('=', 1) line = line.split('=', 1)
if line[0] == 'api_key': if line[0] == 'api_key':
api_key = line[1].strip() api_key = str(line[1].strip())
try: try:
os.remove(join(expanduser('~'), '.wakatime.conf')) os.remove(join(expanduser('~'), '.wakatime.conf'))
except: except:
pass pass
except IOError: except IOError:
pass pass
settings.set('api_key', str(api_key)) SETTINGS.set('api_key', api_key)
sublime.save_settings(SETTINGS) sublime.save_settings(SETTINGS_FILE)
def get_api_key(): def get_api_key():
"""If api key not set, prompt user to enter one then save """If api key not set, prompt user to enter one then save
to WakaTime.sublime-settings. to WakaTime.sublime-settings.
""" """
settings = sublime.load_settings(SETTINGS) global SETTINGS
api_key = settings.get('api_key', '') api_key = SETTINGS.get('api_key', '')
if not api_key: if not api_key:
def got_key(text): def got_key(text):
if text: if text:
settings = sublime.load_settings(SETTINGS) api_key = str(text)
settings.set('api_key', str(text)) SETTINGS.set('api_key', api_key)
sublime.save_settings(SETTINGS) sublime.save_settings(SETTINGS_FILE)
window = sublime.active_window() window = sublime.active_window()
if window is not None: if window:
window.show_input_panel('Enter your WakaTi.me api key:', '', got_key, None, None) window.show_input_panel('Enter your WakaTi.me api key:', '', got_key, None, None)
return sublime.load_settings(SETTINGS).get('api_key', '')
else: else:
print('Error: Could not prompt for api key because no window found.')
return api_key return api_key
def python_binary():
python = 'python'
if platform.system() == 'Windows':
python = 'pythonw'
try:
Popen([python, '--version'])
except:
for path in glob.iglob('/python*'):
if exists(realpath(join(path, 'pythonw.exe'))):
python = realpath(join(path, 'pythonw'))
break
return python
def api(targetFile, timestamp, isWrite=False, endtime=0):
global LAST_ACTION, LAST_USAGE, LAST_FILE
if not targetFile:
targetFile = LAST_FILE
if targetFile:
cmd = [python_binary(), API_CLIENT,
'--file', targetFile,
'--time', str('%f' % timestamp),
'--plugin', 'sublime-wakatime/%s' % __version__,
#'--verbose',
]
api_key = get_api_key()
if api_key:
cmd.extend(['--key', str(api_key)])
if isWrite:
cmd.append('--write')
if endtime:
cmd.extend(['--endtime', str('%f' % endtime)])
#print(cmd)
if platform.system() == 'Windows':
Popen(cmd, shell=False)
else:
with open(join(expanduser('~'), '.wakatime.log'), 'a') as stderr:
Popen(cmd, stderr=stderr)
LAST_ACTION = timestamp
if endtime and endtime > LAST_ACTION:
LAST_ACTION = endtime
LAST_FILE = targetFile
LAST_USAGE = LAST_ACTION
else:
LAST_USAGE = timestamp
def away(now):
duration = now - LAST_USAGE
minutes = ''
units = 'second'
if duration >= 60:
duration = int(duration / 60)
units = 'minute'
if duration >= 60:
remainder = duration % 60
if remainder > 0:
minutes = ' and %d minute' % remainder
if remainder > 1:
minutes = minutes + 's'
duration = int(duration / 60)
units = 'hour'
if duration > 1:
units = units + 's'
return sublime\
.ok_cancel_dialog("You were away %d %s%s. Add time to current file?"\
% (duration, units, minutes), 'Yes, log this time')
def enough_time_passed(now): def enough_time_passed(now):
if now - LAST_ACTION > ACTION_FREQUENCY * 60: if now - LAST_ACTION > ACTION_FREQUENCY * 60:
return True return True
return False return False
def should_prompt_user(now):
if not sublime.load_settings(SETTINGS).get('prompt_after_away', False):
return False
if not LAST_USAGE:
return False
duration = now - LAST_USAGE
if duration > AWAY_MINUTES * 60:
return True
return False
def handle_write_action(view): def handle_write_action(view):
global LAST_USAGE, BUSY thread = SendActionThread(view.file_name(), isWrite=True)
BUSY = True thread.start()
targetFile = view.file_name()
now = time.time()
if enough_time_passed(now) or (LAST_FILE and targetFile != LAST_FILE):
if should_prompt_user(now):
if away(now):
api(targetFile, now, endtime=LAST_ACTION, isWrite=True)
else:
api(targetFile, now, isWrite=True)
else:
api(targetFile, now, endtime=LAST_ACTION, isWrite=True)
else:
api(targetFile, now, isWrite=True)
BUSY = False
def handle_normal_action(view): def handle_normal_action(view):
global LAST_USAGE, BUSY thread = SendActionThread(view.file_name())
BUSY = True thread.start()
targetFile = view.file_name()
now = time.time()
if enough_time_passed(now) or (LAST_FILE and targetFile != LAST_FILE): class SendActionThread(threading.Thread):
if should_prompt_user(now):
if away(now): def __init__(self, targetFile, isWrite=False, force=False):
api(targetFile, now, endtime=LAST_ACTION) threading.Thread.__init__(self)
else: self.targetFile = targetFile
api(targetFile, now) self.isWrite = isWrite
else: self.force = force
api(targetFile, now, endtime=LAST_ACTION)
else: def run(self):
LAST_USAGE = now sublime.set_timeout(self.check, 0)
BUSY = False
def check(self):
global LAST_ACTION, LAST_FILE
if self.targetFile:
self.timestamp = time.time()
if self.force or self.isWrite or self.targetFile != LAST_FILE or enough_time_passed(self.timestamp):
LAST_FILE = self.targetFile
LAST_ACTION = self.timestamp
self.send()
def send(self):
api_key = get_api_key()
if not api_key:
return
cmd = [
API_CLIENT,
'--file', self.targetFile,
'--time', str('%f' % self.timestamp),
'--plugin', 'sublime-wakatime/%s' % __version__,
'--key', str(bytes.decode(api_key.encode('utf8'))),
'--verbose',
]
if self.isWrite:
cmd.append('--write')
#print(cmd)
wakatime.main(cmd)
def plugin_loaded():
setup_settings_file()
# need to call plugin_loaded because only ST3 will auto-call it
if ST_VERSION < 3000:
plugin_loaded()
class WakatimeListener(sublime_plugin.EventListener): class WakatimeListener(sublime_plugin.EventListener):
def on_post_save(self, view): def on_post_save(self, view):
if view.file_name(): global BUSY
#print(['saved', view.file_name()]) if not BUSY:
BUSY = True
handle_write_action(view) handle_write_action(view)
BUSY = False
def on_activated(self, view): def on_activated(self, view):
if view.file_name() and not BUSY: global BUSY
#print(['activated', view.file_name()]) if not BUSY:
BUSY = True
handle_normal_action(view) handle_normal_action(view)
BUSY = False
def on_modified(self, view): def on_modified(self, view):
if view.file_name() and not BUSY: global BUSY
#print(['modified', view.file_name()]) if not BUSY:
BUSY = True
handle_normal_action(view) handle_normal_action(view)
BUSY = False
def plugin_loaded():
get_api_key()
setup_settings_file()
if int(sublime.version()) < 3000:
plugin_loaded()

View file

@ -2,6 +2,11 @@
History History
------- -------
0.3.0 (2013-08-08)
++++++++++++++++++
- Allow importing directly from Python plugins
0.1.1 (2013-07-07) 0.1.1 (2013-07-07)
++++++++++++++++++ ++++++++++++++++++

View file

@ -14,7 +14,7 @@ from __future__ import print_function
import os import os
import sys import sys
sys.path.insert(0, os.path.abspath(__file__)) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import wakatime import wakatime
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -12,7 +12,7 @@
from __future__ import print_function from __future__ import print_function
__title__ = 'wakatime' __title__ = 'wakatime'
__version__ = '0.2.0' __version__ = '0.3.0'
__author__ = 'Alan Hamlett' __author__ = 'Alan Hamlett'
__license__ = 'BSD' __license__ = 'BSD'
__copyright__ = 'Copyright 2013 Alan Hamlett' __copyright__ = 'Copyright 2013 Alan Hamlett'
@ -33,6 +33,7 @@ except ImportError:
from urllib.error import HTTPError from urllib.error import HTTPError
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from .log import setup_logging from .log import setup_logging
from .project import find_project from .project import find_project
try: try:
@ -52,6 +53,10 @@ class FileAction(argparse.Action):
def parseArguments(argv): def parseArguments(argv):
try:
sys.argv
except AttributeError:
sys.argv = argv
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Wakati.Me event api appender') description='Wakati.Me event api appender')
parser.add_argument('--file', dest='targetFile', metavar='file', parser.add_argument('--file', dest='targetFile', metavar='file',

View file

@ -63,6 +63,9 @@ class JsonFormatter(logging.Formatter):
def setup_logging(args, version): def setup_logging(args, version):
logger = logging.getLogger()
if len(logger.handlers) > 0:
return logger
logfile = args.logfile logfile = args.logfile
if not logfile: if not logfile:
logfile = '~/.wakatime.log' logfile = '~/.wakatime.log'
@ -77,7 +80,6 @@ def setup_logging(args, version):
plugin=args.plugin, plugin=args.plugin,
) )
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(handler) logger.addHandler(handler)
level = logging.INFO level = logging.INFO
if args.verbose: if args.verbose: