Compare commits

...

34 Commits

Author SHA1 Message Date
Alan Hamlett c100213351 v11.1.0 2022-11-12 12:34:41 -06:00
Alan Hamlett 43237a05c5 update changes 2022-11-12 12:34:07 -06:00
Alan Hamlett c34eccad4a
Merge pull request #117 from wakatime/feature/api-key-vault-cmd
Support for api key vault cmd config
2022-11-12 12:32:59 -06:00
Alan Hamlett 5077c5e290 read api key vault cmd from sublime settings and wakatime config file 2022-11-12 12:32:43 -06:00
Carlos Henrique Guardão Gandarez a387c08b44
changes for v11.1.0 2022-11-11 21:57:09 -03:00
Carlos Henrique Guardão Gandarez 3c2947cf79
Support for api key vault cmd config 2022-11-11 21:55:29 -03:00
Alan Hamlett 0744d7209b Exit code 112 should be ignored same as 102 2022-11-10 10:45:54 -06:00
Alan Hamlett 54e6772a80 v11.0.8 2022-08-23 15:14:53 +02:00
Alan Hamlett b576dfafe6 changes for v11.0.8 2022-08-23 15:14:40 +02:00
Alan Hamlett 5299efd6fa sometimes selection object can have no elements but eval truthy 2022-08-23 15:13:44 +02:00
Alan Hamlett 74583a6845 v11.0.7 2022-06-25 13:46:38 -03:00
Alan Hamlett 31f1f8ecdc changes for v11.0.7 2022-06-25 13:46:25 -03:00
Alan Hamlett c1f58fd05d check wakatime-cli versions in background thread to prevent blocking UI 2022-06-25 13:45:29 -03:00
Alan Hamlett 28063e3ac4 v11.0.6 2022-06-08 07:44:41 -04:00
Alan Hamlett 4d56aca1a1 changes for v11.0.6 2022-06-08 07:44:01 -04:00
Alan Hamlett e15c514ef3 Fix call to log helper 2022-06-08 07:43:01 -04:00
Alan Hamlett fe582c84b9 v11.0.5 2022-04-29 11:19:26 -07:00
Alan Hamlett d7ee8675d8 changes for v11.0.5 2022-04-29 11:19:05 -07:00
Alan Hamlett da17125b97 Prevent installing global url opener 2022-04-29 11:14:55 -07:00
Alan Hamlett 847223cdce Clean up legacy wakatime-cli directory if exists 2022-02-21 20:08:48 -08:00
Alan Hamlett b405f99cff chmod wakatime-cli after copying 2022-01-21 15:12:51 -08:00
Alan Hamlett b5210b77ce v11.0.4 2022-01-06 18:40:32 -08:00
Alan Hamlett fac1192228 changes for v11.0.4 2022-01-06 18:40:18 -08:00
Alan Hamlett 4d6533b2ee fix lineno, cursorpos, and lines-in-file arguments 2022-01-06 18:37:52 -08:00
Alan Hamlett 7d2bd0b7c5 Copy wakatime-cli when symlink fails 2022-01-06 18:16:02 -08:00
Alan Hamlett 66f5f48f33 create symlink to wakatime-cli 2022-01-06 10:23:09 -08:00
Alan Hamlett e337afcc53 Fix Popen override 2022-01-06 07:56:45 -08:00
Alan Hamlett 033c07f070 prevent using empty cached version 2022-01-05 04:15:10 -08:00
Alan Hamlett c09767b58d v11.0.3 2021-12-31 16:55:16 -08:00
Alan Hamlett ac80c4268b changes for v11.0.3 2021-12-31 16:55:05 -08:00
Alan Hamlett e5331d3086 only remove wakatime-cli after finished downloading new one 2021-12-31 16:42:11 -08:00
Alan Hamlett bebb46dda6 v11.0.2 2021-11-17 14:01:24 -08:00
Alan Hamlett 9767790063 changes for v11.0.2 2021-11-17 14:01:08 -08:00
Alan Hamlett 75c219055d encode cursorpos in json as int not str 2021-11-17 13:52:49 -08:00
3 changed files with 246 additions and 82 deletions

View File

@ -3,6 +3,63 @@ History
-------
11.1.0 (2022-11-12)
++++++++++++++++++
- Support for api key vault cmd config.
`#117 <https://github.com/wakatime/sublime-wakatime/pull/117>`_
11.0.8 (2022-08-23)
++++++++++++++++++
- Bugfix to prevent using empty selection object.
`#116 <https://github.com/wakatime/sublime-wakatime/issues/116>`_
11.0.7 (2022-06-25)
++++++++++++++++++
- Check wakatime-cli versions in background thread.
`#115 <https://github.com/wakatime/sublime-wakatime/issues/115>`_
11.0.6 (2022-06-08)
++++++++++++++++++
- Fix call to log helper.
`#113 <https://github.com/wakatime/sublime-wakatime/issues/113>`_
11.0.5 (2022-04-29)
++++++++++++++++++
- Bugfix to not overwrite global urlopener in embedded Python.
`#110 <https://github.com/wakatime/sublime-wakatime/issues/110>`_
- Chmod wakatime-cli to be executable after updating on non-Windows platforms.
11.0.4 (2022-01-06)
++++++++++++++++++
- Copy wakatime-cli when symlink fails on Windows.
`vim-wakatime#122 <https://github.com/wakatime/vim-wakatime/issues/122>`_
- Fix lineno, cursorpos, and lines-in-file arguments.
11.0.3 (2021-12-31)
++++++++++++++++++
- Bugfix to not delete old wakatime-cli until finished downloading new version.
`#107 <https://github.com/wakatime/sublime-wakatime/issues/107>`_
11.0.2 (2021-11-17)
++++++++++++++++++
- Bugfix to encode extra heartbeats cursorpos as int not str when sending to wakatime-cli.
11.0.1 (2021-11-16)
++++++++++++++++++

View File

@ -4,7 +4,6 @@
[WakaTime][wakatime] is an open source Sublime Text plugin for metrics, insights, and time tracking automatically generated from your programming activity.
## Installation
1. Install [Package Control](https://packagecontrol.io/installation).
@ -19,12 +18,10 @@
6. Use Sublime and your coding activity will be displayed on your [WakaTime dashboard](https://wakatime.com).
## Screen Shots
![Project Overview](https://wakatime.com/static/img/ScreenShots/Screen-Shot-2016-03-21.png)
## Unresponsive Plugin Warning
In Sublime Text 2, if you get a warning message:
@ -35,7 +32,6 @@ To fix this, go to `Preferences → Settings - User` then add the following sett
`"detect_slow_plugins": false`
## Troubleshooting
First, turn on debug mode in your `WakaTime.sublime-settings` file.

View File

@ -8,7 +8,7 @@ Website: https://wakatime.com/
==========================================================="""
__version__ = '11.0.1'
__version__ = '11.1.0'
import sublime
@ -18,6 +18,7 @@ import json
import os
import platform
import re
import shutil
import ssl
import subprocess
import sys
@ -39,9 +40,9 @@ try:
except ImportError:
from configparser import ConfigParser, Error as ConfigParserError
try:
from urllib2 import urlopen, urlretrieve, ProxyHandler, build_opener, install_opener, HTTPError
from urllib2 import Request, urlopen, HTTPError
except ImportError:
from urllib.request import urlopen, urlretrieve, ProxyHandler, build_opener, install_opener
from urllib.request import Request, urlopen
from urllib.error import HTTPError
@ -105,14 +106,14 @@ class Popen(subprocess.Popen):
"""Patched Popen to prevent opening cmd window on Windows platform."""
def __init__(self, *args, **kwargs):
startupinfo = kwargs.get('startupinfo')
if is_win or True:
if is_win:
startupinfo = kwargs.get('startupinfo')
try:
startupinfo = startupinfo or subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
except AttributeError:
pass
kwargs['startupinfo'] = startupinfo
kwargs['startupinfo'] = startupinfo
super(Popen, self).__init__(*args, **kwargs)
@ -185,6 +186,7 @@ class ApiKey(object):
self._key = key
return self._key
configs = None
try:
configs = parseConfigFile(CONFIG_FILE)
if configs:
@ -196,8 +198,38 @@ class ApiKey(object):
except:
pass
key = self.api_key_from_vault_cmd(configs)
if key:
self._key = key
return self._key
return self._key
def api_key_from_vault_cmd(self, configs):
vault_cmd = SETTINGS.get('api_key_vault_cmd')
if not vault_cmd and configs:
try:
if configs.has_option('settings', 'api_key_vault_cmd'):
vault_cmd = configs.get('settings', 'api_key_vault_cmd')
except:
pass
if not vault_cmd or not vault_cmd.strip():
return None
try:
process = Popen(vault_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
stdout, stderr = process.communicate()
retcode = process.poll()
if retcode:
log(ERROR, 'Vault command error ({retcode}): {stderr}'.format(retcode=retcode, stderr=u(stderr)))
return None
return stdout.strip() or None
except:
log(ERROR, traceback.format_exc())
return None
def write(self, key):
global SETTINGS
self._key = key
@ -416,10 +448,16 @@ def append_heartbeat(entity, timestamp, is_write, view, project, folders):
'entity': entity,
'timestamp': timestamp,
'is_write': is_write,
'cursorpos': view.sel()[0].begin() if view.sel() else None,
'project': project,
'folders': folders,
'lines_in_file': view.rowcol(view.size())[0] + 1,
}
selections = view.sel()
if selections and len(selections) > 0:
rowcol = view.rowcol(selections[0].begin())
row, col = rowcol[0] + 1, rowcol[1] + 1
heartbeat['lineno'] = row
heartbeat['cursorpos'] = col
HEARTBEATS.put_nowait(heartbeat)
# make this heartbeat the LAST_HEARTBEAT
@ -492,7 +530,8 @@ class SendHeartbeatsThread(threading.Thread):
self.send_heartbeats()
def build_heartbeat(self, entity=None, timestamp=None, is_write=None,
cursorpos=None, project=None, folders=None):
lineno=None, cursorpos=None, lines_in_file=None,
project=None, folders=None):
"""Returns a dict for passing to wakatime-cli as arguments."""
heartbeat = {
@ -508,8 +547,12 @@ class SendHeartbeatsThread(threading.Thread):
if project_name:
heartbeat['alternate_project'] = project_name
if lineno is not None:
heartbeat['lineno'] = lineno
if cursorpos is not None:
heartbeat['cursorpos'] = '{0}'.format(cursorpos)
heartbeat['cursorpos'] = cursorpos
if lines_in_file is not None:
heartbeat['lines-in-file'] = lines_in_file
return heartbeat
@ -528,8 +571,12 @@ class SendHeartbeatsThread(threading.Thread):
cmd.append('--write')
if heartbeat.get('alternate_project'):
cmd.extend(['--alternate-project', heartbeat['alternate_project']])
if heartbeat.get('lineno') is not None:
cmd.extend(['--lineno', '{0}'.format(heartbeat['lineno'])])
if heartbeat.get('cursorpos') is not None:
cmd.extend(['--cursorpos', heartbeat['cursorpos']])
cmd.extend(['--cursorpos', '{0}'.format(heartbeat['cursorpos'])])
if heartbeat.get('lines_in_file') is not None:
cmd.extend(['--lines-in-file', '{0}'.format(heartbeat['lines_in_file'])])
for pattern in self.ignore:
cmd.extend(['--exclude', pattern])
for pattern in self.include:
@ -555,12 +602,12 @@ class SendHeartbeatsThread(threading.Thread):
process = Popen(cmd, stdin=stdin, stdout=PIPE, stderr=STDOUT)
output, _err = process.communicate(input=inp)
retcode = process.poll()
if (not retcode or retcode == 102) and not output:
if (not retcode or retcode == 102 or retcode == 112) and not output:
self.sent()
else:
update_status_bar('Error')
if retcode:
log(DEBUG if retcode == 102 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
log(DEBUG if retcode == 102 or retcode == 112 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
if output:
log(ERROR, u('wakatime-core output: {0}').format(output))
except:
@ -578,9 +625,7 @@ def plugin_loaded():
log(INFO, 'Initializing WakaTime plugin v%s' % __version__)
update_status_bar('Initializing...')
if not isCliLatest():
thread = DownloadCLI()
thread.start()
UpdateCLI().start()
after_loaded()
@ -616,28 +661,34 @@ class WakatimeDashboardCommand(sublime_plugin.ApplicationCommand):
webbrowser.open_new_tab('https://wakatime.com/dashboard')
class DownloadCLI(threading.Thread):
class UpdateCLI(threading.Thread):
"""Non-blocking thread for downloading latest wakatime-cli from GitHub.
"""
def run(self):
if isCliLatest():
return
log(INFO, 'Downloading wakatime-cli...')
if os.path.isdir(os.path.join(RESOURCES_FOLDER, 'wakatime-cli')):
shutil.rmtree(os.path.join(RESOURCES_FOLDER, 'wakatime-cli'))
if not os.path.exists(RESOURCES_FOLDER):
os.makedirs(RESOURCES_FOLDER)
if isCliInstalled():
try:
os.remove(getCliLocation())
except:
log(DEBUG, traceback.format_exc())
try:
url = cliDownloadUrl()
log(DEBUG, 'Downloading wakatime-cli from {url}'.format(url=url))
zip_file = os.path.join(RESOURCES_FOLDER, 'wakatime-cli.zip')
download(url, zip_file)
if isCliInstalled():
try:
os.remove(getCliLocation())
except:
log(DEBUG, traceback.format_exc())
log(INFO, 'Extracting wakatime-cli...')
with ZipFile(zip_file) as zf:
zf.extractall(RESOURCES_FOLDER)
@ -652,6 +703,8 @@ class DownloadCLI(threading.Thread):
except:
log(DEBUG, traceback.format_exc())
createSymlink()
log(INFO, 'Finished extracting wakatime-cli.')
@ -725,10 +778,7 @@ def getLatestCliVersion():
try:
configs = parseConfigFile(INTERNAL_CONFIG_FILE)
if configs:
if configs.has_option('internal', 'cli_version'):
last_version = configs.get('internal', 'cli_version')
if last_version and configs.has_option('internal', 'cli_version_last_modified'):
last_modified = configs.get('internal', 'cli_version_last_modified')
last_modified, last_version = lastModifiedAndVersion(configs)
except:
log(DEBUG, traceback.format_exc())
@ -762,6 +812,17 @@ def getLatestCliVersion():
return None
def lastModifiedAndVersion(configs):
last_modified, last_version = None, None
if configs.has_option('internal', 'cli_version'):
last_version = configs.get('internal', 'cli_version')
if last_version and configs.has_option('internal', 'cli_version_last_modified'):
last_modified = configs.get('internal', 'cli_version_last_modified')
if last_modified and last_version and extractVersion(last_version):
return last_modified, last_version
return None, None
def extractVersion(text):
pattern = re.compile(r"([0-9]+\.[0-9]+\.[0-9]+)")
match = pattern.search(text)
@ -818,80 +879,130 @@ def reportMissingPlatformSupport(osname, arch):
def request(url, last_modified=None):
req = Request(url)
req.add_header('User-Agent', 'github.com/wakatime/sublime-wakatime')
proxy = SETTINGS.get('proxy')
if proxy:
opener = build_opener(ProxyHandler({
'http': proxy,
'https': proxy,
}))
else:
opener = build_opener()
req.set_proxy(proxy, 'https')
headers = [('User-Agent', 'github.com/wakatime/sublime-wakatime')]
if last_modified:
headers.append(('If-Modified-Since', last_modified))
opener.addheaders = headers
install_opener(opener)
req.add_header('If-Modified-Since', last_modified)
try:
resp = urlopen(url)
resp = urlopen(req)
headers = dict(resp.getheaders()) if is_py2 else resp.headers
return headers, resp.read(), resp.getcode()
except HTTPError as err:
if err.code == 304:
return None, None, 304
if is_py2:
ssl._create_default_https_context = ssl._create_unverified_context
try:
resp = urlopen(url)
headers = dict(resp.getheaders()) if is_py2 else resp.headers
return headers, resp.read(), resp.getcode()
except HTTPError as err2:
if err2.code == 304:
return None, None, 304
log(DEBUG, err.read().decode())
log(DEBUG, err2.read().decode())
raise
except IOError:
raise
with SSLCertVerificationDisabled():
try:
resp = urlopen(req)
headers = dict(resp.getheaders()) if is_py2 else resp.headers
return headers, resp.read(), resp.getcode()
except HTTPError as err2:
if err2.code == 304:
return None, None, 304
log(DEBUG, err.read().decode())
log(DEBUG, err2.read().decode())
raise
except IOError:
raise
log(DEBUG, err.read().decode())
raise
except IOError:
if is_py2:
ssl._create_default_https_context = ssl._create_unverified_context
try:
resp = urlopen(url)
headers = dict(resp.getheaders()) if is_py2 else resp.headers
return headers, resp.read(), resp.getcode()
except HTTPError as err:
if err.code == 304:
return None, None, 304
log(DEBUG, err.read().decode())
raise
except IOError:
raise
with SSLCertVerificationDisabled():
try:
resp = urlopen(url)
headers = dict(resp.getheaders()) if is_py2 else resp.headers
return headers, resp.read(), resp.getcode()
except HTTPError as err:
if err.code == 304:
return None, None, 304
log(DEBUG, err.read().decode())
raise
except IOError:
raise
raise
def download(url, filePath):
req = Request(url)
req.add_header('User-Agent', 'github.com/wakatime/sublime-wakatime')
proxy = SETTINGS.get('proxy')
if proxy:
opener = build_opener(ProxyHandler({
'http': proxy,
'https': proxy,
}))
else:
opener = build_opener()
opener.addheaders = [('User-Agent', 'github.com/wakatime/sublime-wakatime')]
req.set_proxy(proxy, 'https')
install_opener(opener)
with open(filePath, 'wb') as fh:
try:
resp = urlopen(req)
fh.write(resp.read())
except HTTPError as err:
if err.code == 304:
return None, None, 304
if is_py2:
with SSLCertVerificationDisabled():
try:
resp = urlopen(req)
fh.write(resp.read())
return
except HTTPError as err2:
log(DEBUG, err.read().decode())
log(DEBUG, err2.read().decode())
raise
except IOError:
raise
log(DEBUG, err.read().decode())
raise
except IOError:
if is_py2:
with SSLCertVerificationDisabled():
try:
resp = urlopen(url)
fh.write(resp.read())
return
except HTTPError as err:
log(DEBUG, err.read().decode())
raise
except IOError:
raise
raise
def is_symlink(path):
try:
return os.is_symlink(path)
except:
return False
def createSymlink():
link = os.path.join(RESOURCES_FOLDER, 'wakatime-cli')
if is_win:
link = link + '.exe'
elif os.path.exists(link) and is_symlink(link):
return # don't re-create symlink on Unix-like platforms
try:
urlretrieve(url, filePath)
except IOError:
if is_py2:
ssl._create_default_https_context = ssl._create_unverified_context
urlretrieve(url, filePath)
raise
os.symlink(getCliLocation(), link)
except:
try:
shutil.copy2(getCliLocation(), link)
if not is_win:
os.chmod(link, 509) # 755
except:
log(WARNING, traceback.format_exc())
class SSLCertVerificationDisabled(object):
def __enter__(self):
self.original_context = ssl._create_default_https_context
ssl._create_default_https_context = ssl._create_unverified_context
def __exit__(self, *args, **kwargs):
ssl._create_default_https_context = self.original_context