Compare commits

..

15 Commits

2 changed files with 215 additions and 61 deletions

View File

@ -3,6 +3,44 @@ History
-------
6.0.1 (2016-01-01)
++++++++++++++++++
- use embedded python if system python is broken, or doesn't output a version number
- log output from wakatime-cli in ST console when in debug mode
6.0.0 (2015-12-01)
++++++++++++++++++
- use embeddable Python instead of installing on Windows
5.0.1 (2015-10-06)
++++++++++++++++++
- look for python in system PATH again
5.0.0 (2015-10-02)
++++++++++++++++++
- improve logging with levels and log function
- switch registry warnings to debug log level
4.0.20 (2015-10-01)
++++++++++++++++++
- correctly find python binary in non-Windows environments
4.0.19 (2015-10-01)
++++++++++++++++++
- handle case where ST builtin python does not have _winreg or winreg module
4.0.18 (2015-10-01)
++++++++++++++++++

View File

@ -7,7 +7,7 @@ Website: https://wakatime.com/
==========================================================="""
__version__ = '4.0.18'
__version__ = '6.0.1'
import sublime
@ -22,11 +22,58 @@ import threading
import urllib
import webbrowser
from datetime import datetime
from subprocess import Popen
from zipfile import ZipFile
from subprocess import Popen, STDOUT, PIPE
try:
import _winreg as winreg # py2
except ImportError:
import winreg # py3
try:
import winreg # py3
except ImportError:
winreg = None
is_py2 = (sys.version_info[0] == 2)
is_py3 = (sys.version_info[0] == 3)
if is_py2:
def u(text):
if text is None:
return None
try:
return text.decode('utf-8')
except:
try:
return text.decode(sys.getdefaultencoding())
except:
try:
return unicode(text)
except:
return text
elif is_py3:
def u(text):
if text is None:
return None
if isinstance(text, bytes):
try:
return text.decode('utf-8')
except:
try:
return text.decode(sys.getdefaultencoding())
except:
pass
try:
return str(text)
except:
return text
else:
raise Exception('Unsupported Python version: {0}.{1}.{2}'.format(
sys.version_info[0],
sys.version_info[1],
sys.version_info[2],
))
# globals
@ -45,6 +92,13 @@ LOCK = threading.RLock()
PYTHON_LOCATION = None
# Log Levels
DEBUG = 'DEBUG'
INFO = 'INFO'
WARNING = 'WARNING'
ERROR = 'ERROR'
# add wakatime package to path
sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages'))
try:
@ -53,6 +107,20 @@ except ImportError:
pass
def log(lvl, message, *args, **kwargs):
try:
if lvl == DEBUG and not SETTINGS.get('debug'):
return
msg = message
if len(args) > 0:
msg = message.format(*args)
elif len(kwargs) > 0:
msg = message.format(**kwargs)
print('[WakaTime] [{lvl}] {msg}'.format(lvl=lvl, msg=msg))
except RuntimeError:
sublime.set_timeout(lambda: log(lvl, message, *args, **kwargs), 0)
def createConfigFile():
"""Creates the .wakatime.cfg INI file in $HOME directory, if it does
not already exist.
@ -97,43 +165,49 @@ def prompt_api_key():
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None)
return True
else:
print('[WakaTime] Error: Could not prompt for api key because no window found.')
log(ERROR, 'Could not prompt for api key because no window found.')
return False
def python_binary():
global PYTHON_LOCATION
if PYTHON_LOCATION is not None:
return PYTHON_LOCATION
# look for python in PATH and common install locations
paths = [
"pythonw",
"python",
"/usr/local/bin/python",
"/usr/bin/python",
os.path.join(os.path.expanduser('~'), '.wakatime', 'python'),
None,
'/',
'/usr/local/bin/',
'/usr/bin/',
]
for path in paths:
path = find_python_in_folder(path)
if path is not None:
PYTHON_LOCATION = path
set_python_binary_location(path)
return path
# look for python in windows registry
path = find_python_from_registry(r'SOFTWARE\Python\PythonCore')
if path is not None:
PYTHON_LOCATION = path
set_python_binary_location(path)
return path
path = find_python_from_registry(r'SOFTWARE\Wow6432Node\Python\PythonCore')
if path is not None:
PYTHON_LOCATION = path
set_python_binary_location(path)
return path
return None
def set_python_binary_location(path):
global PYTHON_LOCATION
PYTHON_LOCATION = path
log(DEBUG, 'Found Python at: {0}'.format(path))
def find_python_from_registry(location, reg=None):
if platform.system() != 'Windows':
if platform.system() != 'Windows' or winreg is None:
return None
if reg is None:
@ -166,36 +240,55 @@ def find_python_from_registry(location, reg=None):
if path is not None:
path = find_python_in_folder(path)
if path is not None:
log(DEBUG, 'Found python from {reg}\\{key}\\{version}\\{sub_key}.'.format(
reg=reg,
key=location,
version=version,
sub_key=sub_key,
))
return path
except WindowsError:
print('[WakaTime] Warning: Could not read registry value "{reg}\\{key}\\{version}\\{sub_key}".'.format(
reg='HKEY_CURRENT_USER',
log(DEBUG, 'Could not read registry value "{reg}\\{key}\\{version}\\{sub_key}".'.format(
reg=reg,
key=location,
version=version,
sub_key=sub_key,
))
except WindowsError:
print('[WakaTime] Warning: Could not read registry value "{reg}\\{key}".'.format(
reg='HKEY_CURRENT_USER',
key=location,
))
if SETTINGS.get('debug'):
log(DEBUG, 'Could not read registry value "{reg}\\{key}".'.format(
reg=reg,
key=location,
))
return val
def find_python_in_folder(folder):
path = os.path.realpath(os.path.join(folder, 'pythonw'))
def find_python_in_folder(folder, headless=True):
pattern = re.compile(r'\d+\.\d+')
path = 'python'
if folder is not None:
path = os.path.realpath(os.path.join(folder, 'python'))
if headless:
path = u(path) + u('w')
log(DEBUG, u('Looking for Python at: {0}').format(path))
try:
Popen([path, '--version'])
return path
process = Popen([path, '--version'], stdout=PIPE, stderr=STDOUT)
output, err = process.communicate()
output = u(output).strip()
retcode = process.poll()
log(DEBUG, u('Python Version Output: {0}').format(output))
if not retcode and pattern.search(output):
return path
except:
pass
path = os.path.realpath(os.path.join(folder, 'python'))
try:
Popen([path, '--version'])
return path
except:
pass
log(DEBUG, u('Python Version Output: {0}').format(u(sys.exc_info()[1])))
if headless:
path = find_python_in_folder(folder, headless=False)
if path is not None:
return path
return None
@ -296,7 +389,7 @@ class SendHeartbeatThread(threading.Thread):
def send_heartbeat(self):
if not self.api_key:
print('[WakaTime] Error: missing api key.')
log(ERROR, 'missing api key.')
return
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
cmd = [
@ -322,16 +415,26 @@ class SendHeartbeatThread(threading.Thread):
cmd.append('--verbose')
if python_binary():
cmd.insert(0, python_binary())
if self.debug:
print('[WakaTime] %s' % ' '.join(obfuscate_apikey(cmd)))
if platform.system() == 'Windows':
Popen(cmd, shell=False)
else:
with open(os.path.join(os.path.expanduser('~'), '.wakatime.log'), 'a') as stderr:
Popen(cmd, stderr=stderr)
self.sent()
log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
try:
if not self.debug:
Popen(cmd)
self.sent()
else:
process = Popen(cmd, stdout=PIPE, stderr=STDOUT)
output, err = process.communicate()
output = u(output)
retcode = process.poll()
if (not retcode or retcode == 102) and not output:
self.sent()
if retcode:
log(DEBUG if retcode == 102 else ERROR, 'wakatime-core exited with status: {0}'.format(retcode))
if output:
log(ERROR, u('wakatime-core output: {0}').format(output))
except:
log(ERROR, u(sys.exc_info()[1]))
else:
print('[WakaTime] Error: Unable to find python binary.')
log(ERROR, 'Unable to find python binary.')
def sent(self):
sublime.set_timeout(self.set_status_bar, 0)
@ -350,44 +453,57 @@ class SendHeartbeatThread(threading.Thread):
}
class InstallPython(threading.Thread):
"""Non-blocking thread for installing Python on Windows machines.
class DownloadPython(threading.Thread):
"""Non-blocking thread for extracting embeddable Python on Windows machines.
"""
def run(self):
print('[WakaTime] Downloading and installing python...')
url = 'https://www.python.org/ftp/python/3.4.3/python-3.4.3.msi'
if platform.architecture()[0] == '64bit':
url = 'https://www.python.org/ftp/python/3.4.3/python-3.4.3.amd64.msi'
python_msi = os.path.join(os.path.expanduser('~'), 'python.msi')
log(INFO, 'Downloading embeddable Python...')
ver = '3.5.0'
arch = 'amd64' if platform.architecture()[0] == '64bit' else 'win32'
url = 'https://www.python.org/ftp/python/{ver}/python-{ver}-embed-{arch}.zip'.format(
ver=ver,
arch=arch,
)
if not os.path.exists(os.path.join(os.path.expanduser('~'), '.wakatime')):
os.makedirs(os.path.join(os.path.expanduser('~'), '.wakatime'))
zip_file = os.path.join(os.path.expanduser('~'), '.wakatime', 'python.zip')
try:
urllib.urlretrieve(url, python_msi)
urllib.urlretrieve(url, zip_file)
except AttributeError:
urllib.request.urlretrieve(url, python_msi)
args = [
'msiexec',
'/i',
python_msi,
'/norestart',
'/qb!',
]
Popen(args)
urllib.request.urlretrieve(url, zip_file)
log(INFO, 'Extracting Python...')
with ZipFile(zip_file) as zf:
path = os.path.join(os.path.expanduser('~'), '.wakatime', 'python')
zf.extractall(path)
try:
os.remove(zip_file)
except:
pass
log(INFO, 'Finished extracting Python.')
def plugin_loaded():
global SETTINGS
print('[WakaTime] Initializing WakaTime plugin v%s' % __version__)
log(INFO, 'Initializing WakaTime plugin v%s' % __version__)
SETTINGS = sublime.load_settings(SETTINGS_FILE)
if not python_binary():
print('[WakaTime] Warning: Python binary not found.')
log(WARNING, 'Python binary not found.')
if platform.system() == 'Windows':
thread = InstallPython()
thread = DownloadPython()
thread.start()
else:
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
return
SETTINGS = sublime.load_settings(SETTINGS_FILE)
after_loaded()