Compare commits

..

14 Commits

2 changed files with 211 additions and 46 deletions

View File

@ -3,6 +3,43 @@ History
------- -------
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)
++++++++++++++++++
- find python location from windows registry
4.0.17 (2015-10-01) 4.0.17 (2015-10-01)
++++++++++++++++++ ++++++++++++++++++

View File

@ -7,22 +7,30 @@ Website: https://wakatime.com/
===========================================================""" ==========================================================="""
__version__ = '4.0.17' __version__ = '6.0.0'
import sublime import sublime
import sublime_plugin import sublime_plugin
import glob
import os import os
import platform import platform
import re
import sys import sys
import time import time
import threading import threading
import urllib import urllib
import webbrowser import webbrowser
from datetime import datetime from datetime import datetime
from zipfile import ZipFile
from subprocess import Popen from subprocess import Popen
try:
import _winreg as winreg # py2
except ImportError:
try:
import winreg # py3
except ImportError:
winreg = None
# globals # globals
@ -41,6 +49,13 @@ LOCK = threading.RLock()
PYTHON_LOCATION = None PYTHON_LOCATION = None
# Log Levels
DEBUG = 'DEBUG'
INFO = 'INFO'
WARNING = 'WARNING'
ERROR = 'ERROR'
# add wakatime package to path # add wakatime package to path
sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages')) sys.path.insert(0, os.path.join(PLUGIN_DIR, 'packages'))
try: try:
@ -49,6 +64,20 @@ except ImportError:
pass 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(): def createConfigFile():
"""Creates the .wakatime.cfg INI file in $HOME directory, if it does """Creates the .wakatime.cfg INI file in $HOME directory, if it does
not already exist. not already exist.
@ -93,35 +122,122 @@ def prompt_api_key():
window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None) window.show_input_panel('[WakaTime] Enter your wakatime.com api key:', default_key, got_key, None, None)
return True return True
else: 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 return False
def python_binary(): def python_binary():
global PYTHON_LOCATION
if PYTHON_LOCATION is not None: if PYTHON_LOCATION is not None:
return PYTHON_LOCATION return PYTHON_LOCATION
# look for python in PATH and common install locations
paths = [ paths = [
"pythonw", os.path.join(os.path.expanduser('~'), '.wakatime', 'python'),
"python", None,
"/usr/local/bin/python", '/',
"/usr/bin/python", '/usr/local/bin/',
'/usr/bin/',
] ]
for path in paths: for path in paths:
try: path = find_python_in_folder(path)
Popen([path, '--version']) if path is not None:
PYTHON_LOCATION = path set_python_binary_location(path)
return path return path
except:
pass # look for python in windows registry
for path in glob.iglob('/python*'): path = find_python_from_registry(r'SOFTWARE\Python\PythonCore')
path = os.path.realpath(os.path.join(path, 'pythonw')) if path is not None:
try: set_python_binary_location(path)
Popen([path, '--version']) return path
PYTHON_LOCATION = path path = find_python_from_registry(r'SOFTWARE\Wow6432Node\Python\PythonCore')
return path if path is not None:
except: set_python_binary_location(path)
pass return path
return None
def set_python_binary_location(path):
global PYTHON_LOCATION
PYTHON_LOCATION = path
log(DEBUG, 'Python Binary Found: {0}'.format(path))
def find_python_from_registry(location, reg=None):
if platform.system() != 'Windows' or winreg is None:
return None
if reg is None:
path = find_python_from_registry(location, reg=winreg.HKEY_CURRENT_USER)
if path is None:
path = find_python_from_registry(location, reg=winreg.HKEY_LOCAL_MACHINE)
return path
val = None
sub_key = 'InstallPath'
compiled = re.compile(r'^\d+\.\d+$')
try:
with winreg.OpenKey(reg, location) as handle:
versions = []
try:
for index in range(1024):
version = winreg.EnumKey(handle, index)
try:
if compiled.search(version):
versions.append(version)
except re.error:
pass
except EnvironmentError:
pass
versions.sort(reverse=True)
for version in versions:
try:
path = winreg.QueryValue(handle, version + '\\' + sub_key)
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:
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:
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 = 'pythonw'
if folder is not None:
path = os.path.realpath(os.path.join(folder, 'pythonw'))
try:
Popen([path, '--version'])
return path
except:
pass
path = 'python'
if folder is not None:
path = os.path.realpath(os.path.join(folder, 'python'))
try:
Popen([path, '--version'])
return path
except:
pass
return None return None
@ -222,7 +338,7 @@ class SendHeartbeatThread(threading.Thread):
def send_heartbeat(self): def send_heartbeat(self):
if not self.api_key: if not self.api_key:
print('[WakaTime] Error: missing api key.') log(ERROR, 'missing api key.')
return return
ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__) ua = 'sublime/%d sublime-wakatime/%s' % (ST_VERSION, __version__)
cmd = [ cmd = [
@ -248,8 +364,7 @@ class SendHeartbeatThread(threading.Thread):
cmd.append('--verbose') cmd.append('--verbose')
if python_binary(): if python_binary():
cmd.insert(0, python_binary()) cmd.insert(0, python_binary())
if self.debug: log(DEBUG, ' '.join(obfuscate_apikey(cmd)))
print('[WakaTime] %s' % ' '.join(obfuscate_apikey(cmd)))
if platform.system() == 'Windows': if platform.system() == 'Windows':
Popen(cmd, shell=False) Popen(cmd, shell=False)
else: else:
@ -257,7 +372,7 @@ class SendHeartbeatThread(threading.Thread):
Popen(cmd, stderr=stderr) Popen(cmd, stderr=stderr)
self.sent() self.sent()
else: else:
print('[WakaTime] Error: Unable to find python binary.') log(ERROR, 'Unable to find python binary.')
def sent(self): def sent(self):
sublime.set_timeout(self.set_status_bar, 0) sublime.set_timeout(self.set_status_bar, 0)
@ -276,44 +391,57 @@ class SendHeartbeatThread(threading.Thread):
} }
class InstallPython(threading.Thread): class DownloadPython(threading.Thread):
"""Non-blocking thread for installing Python on Windows machines. """Non-blocking thread for extracting embeddable Python on Windows machines.
""" """
def run(self): def run(self):
print('[WakaTime] Downloading and installing python...') log(INFO, 'Downloading embeddable Python...')
url = 'https://www.python.org/ftp/python/3.4.3/python-3.4.3.msi'
if platform.architecture()[0] == '64bit': ver = '3.5.0'
url = 'https://www.python.org/ftp/python/3.4.3/python-3.4.3.amd64.msi' arch = 'amd64' if platform.architecture()[0] == '64bit' else 'win32'
python_msi = os.path.join(os.path.expanduser('~'), 'python.msi') 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: try:
urllib.urlretrieve(url, python_msi) urllib.urlretrieve(url, zip_file)
except AttributeError: except AttributeError:
urllib.request.urlretrieve(url, python_msi) urllib.request.urlretrieve(url, zip_file)
args = [
'msiexec', log(INFO, 'Extracting Python...')
'/i', with ZipFile(zip_file) as zf:
python_msi, path = os.path.join(os.path.expanduser('~'), '.wakatime', 'python')
'/norestart', zf.extractall(path)
'/qb!',
] try:
Popen(args) os.remove(zip_file)
except:
pass
log(INFO, 'Finished extracting Python.')
def plugin_loaded(): def plugin_loaded():
global SETTINGS 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(): if not python_binary():
print('[WakaTime] Warning: Python binary not found.') log(WARNING, 'Python binary not found.')
if platform.system() == 'Windows': if platform.system() == 'Windows':
thread = InstallPython() thread = DownloadPython()
thread.start() thread.start()
else: else:
sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads") sublime.error_message("Unable to find Python binary!\nWakaTime needs Python to work correctly.\n\nGo to https://www.python.org/downloads")
return return
SETTINGS = sublime.load_settings(SETTINGS_FILE)
after_loaded() after_loaded()