Skip to content

Commit

Permalink
Split the source into individual files for easier maintenance. Added …
Browse files Browse the repository at this point in the history
…get_cache(), set_cache(), console_write() and show_error() in the process.
  • Loading branch information
wbond committed Jan 23, 2013
1 parent 44cfe2a commit e509241
Show file tree
Hide file tree
Showing 64 changed files with 5,250 additions and 4,810 deletions.
4,841 changes: 31 additions & 4,810 deletions Package Control.py

Large diffs are not rendered by default.

Empty file added package_control/__init__.py
Empty file.
158 changes: 158 additions & 0 deletions package_control/automatic_upgrader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import sublime
import threading
import re
import os
import datetime
import time

from .console_write import console_write
from .package_installer import PackageInstaller
from .package_renamer import PackageRenamer


class AutomaticUpgrader(threading.Thread):
"""
Automatically checks for updated packages and installs them. controlled
by the `auto_upgrade`, `auto_upgrade_ignore`, `auto_upgrade_frequency` and
`auto_upgrade_last_run` settings.
"""

def __init__(self, found_packages):
"""
:param found_packages:
A list of package names for the packages that were found to be
installed on the machine.
"""

self.installer = PackageInstaller()
self.manager = self.installer.manager

self.load_settings()

self.package_renamer = PackageRenamer()
self.package_renamer.load_settings()

self.auto_upgrade = self.settings.get('auto_upgrade')
self.auto_upgrade_ignore = self.settings.get('auto_upgrade_ignore')

self.next_run = int(time.time())
self.last_run = None
last_run_file = os.path.join(sublime.packages_path(), 'User',
'Package Control.last-run')

if os.path.isfile(last_run_file):
with open(last_run_file) as fobj:
try:
self.last_run = int(fobj.read())
except ValueError:
pass

frequency = self.settings.get('auto_upgrade_frequency')
if frequency:
if self.last_run:
self.next_run = int(self.last_run) + (frequency * 60 * 60)
else:
self.next_run = time.time()

# Detect if a package is missing that should be installed
self.missing_packages = list(set(self.installed_packages) -
set(found_packages))

if self.auto_upgrade and self.next_run <= time.time():
with open(last_run_file, 'w') as fobj:
fobj.write(str(int(time.time())))

threading.Thread.__init__(self)

def load_settings(self):
"""
Loads the list of installed packages from the
Package Control.sublime-settings file
"""

self.settings_file = 'Package Control.sublime-settings'
self.settings = sublime.load_settings(self.settings_file)
self.installed_packages = self.settings.get('installed_packages', [])
self.should_install_missing = self.settings.get('install_missing')
if not isinstance(self.installed_packages, list):
self.installed_packages = []

def run(self):
self.install_missing()

if self.next_run > time.time():
self.print_skip()
return

self.upgrade_packages()

def install_missing(self):
"""
Installs all packages that were listed in the list of
`installed_packages` from Package Control.sublime-settings but were not
found on the filesystem and passed as `found_packages`.
"""

if not self.missing_packages or not self.should_install_missing:
return

console_write(u'Installing %s missing packages' % len(self.missing_packages), True)
for package in self.missing_packages:
if self.installer.manager.install_package(package):
console_write(u'Installed missing package %s' % package, True)

def print_skip(self):
"""
Prints a notice in the console if the automatic upgrade is skipped
due to already having been run in the last `auto_upgrade_frequency`
hours.
"""

last_run = datetime.datetime.fromtimestamp(self.last_run)
next_run = datetime.datetime.fromtimestamp(self.next_run)
date_format = '%Y-%m-%d %H:%M:%S'
message_string = u'Skipping automatic upgrade, last run at %s, next run at %s or after' % (
last_run.strftime(date_format), next_run.strftime(date_format))
console_write(message_string, True)

def upgrade_packages(self):
"""
Upgrades all packages that are not currently upgraded to the lastest
version. Also renames any installed packages to their new names.
"""

if not self.auto_upgrade:
return

self.package_renamer.rename_packages(self.installer)

packages = self.installer.make_package_list(['install',
'reinstall', 'downgrade', 'overwrite', 'none'],
ignore_packages=self.auto_upgrade_ignore)

# If Package Control is being upgraded, just do that and restart
for package in packages:
if package[0] != 'Package Control':
continue

def reset_last_run():
settings = sublime.load_settings(self.settings_file)
settings.set('auto_upgrade_last_run', None)
sublime.save_settings(self.settings_file)
sublime.set_timeout(reset_last_run, 1)
packages = [package]
break

if not packages:
console_write(u'No updated packages', True)
return

console_write(u'Installing %s upgrades' % len(packages), True)
for package in packages:
self.installer.manager.install_package(package[0])
version = re.sub('^.*?(v[\d\.]+).*?$', '\\1', package[2])
if version == package[2] and version.find('pull with') != -1:
vcs = re.sub('^pull with (\w+).*?$', '\\1', version)
version = 'latest %s commit' % vcs
message_string = u'Upgraded %s to %s' % (package[0], version)
console_write(message_string, True)
48 changes: 48 additions & 0 deletions package_control/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import time


# A cache of channel and repository info to allow users to install multiple
# packages without having to wait for the metadata to be downloaded more
# than once. The keys are managed locally by the utilizing code.
_channel_repository_cache = {}


def set_cache(key, data, ttl=300):
"""
Sets an in-memory cache value
:param key:
The string key
:param data:
The data to cache
:param ttl:
The integer number of second to cache the data for
"""

_channel_repository_cache[key] = {
'data': data,
'expires': time.time() + ttl
}


def get_cache(key, default=None):
"""
Gets an in-memory cache value
:param key:
The string key
:param default:
The value to return if the key has not been set, or the ttl expired
:return:
The cached value, or default
"""

struct = _channel_repository_cache.get(key, {})
expires = struct.get('expires')
if expires and expires > time.time():
return struct.get('data')
return default
37 changes: 37 additions & 0 deletions package_control/clear_directory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import os


def clear_directory(directory, ignore_paths=None):
"""
Tries to delete all files and folders from a directory
:param directory:
The string directory path
:param ignore_paths:
An array of paths to ignore while deleting files
:return:
If all of the files and folders were successfully deleted
"""

was_exception = False
for root, dirs, files in os.walk(directory, topdown=False):
paths = [os.path.join(root, f) for f in files]
paths.extend([os.path.join(root, d) for d in dirs])

for path in paths:
try:
# Don't delete the metadata file, that way we have it
# when the reinstall happens, and the appropriate
# usage info can be sent back to the server
if ignore_paths and path in ignore_paths:
continue
if os.path.isdir(path):
os.rmdir(path)
else:
os.remove(path)
except (OSError, IOError):
was_exception = True

return not was_exception
32 changes: 32 additions & 0 deletions package_control/cmd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import os
import subprocess
import re


def create_cmd(args, basename_binary=False):
"""
Takes an array of strings to be passed to subprocess.Popen and creates
a string that can be pasted into a terminal
:param args:
The array containing the binary name/path and all arguments
:param basename_binary:
If only the basename of the binary should be disabled instead of the full path
:return:
The command string
"""

if basename_binary:
args[0] = os.path.basename(args[0])

if os.name == 'nt':
return subprocess.list2cmdline(args)
else:
escaped_args = []
for arg in args:
if re.search('^[a-zA-Z0-9/_^\\-\\.:=]+$', arg) == None:
arg = u"'" + arg.replace(u"'", u"'\\''") + u"'"
escaped_args.append(arg)
return u' '.join(escaped_args)
Empty file.
36 changes: 36 additions & 0 deletions package_control/commands/add_repository_channel_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sublime
import sublime_plugin


class AddRepositoryChannelCommand(sublime_plugin.WindowCommand):
"""
A command to add a new channel (list of repositories) to the user's machine
"""

def run(self):
self.window.show_input_panel('Channel JSON URL', '',
self.on_done, self.on_change, self.on_cancel)

def on_done(self, input):
"""
Input panel handler - adds the provided URL as a channel
:param input:
A string of the URL to the new channel
"""

settings = sublime.load_settings('Package Control.sublime-settings')
repository_channels = settings.get('repository_channels', [])
if not repository_channels:
repository_channels = []
repository_channels.append(input)
settings.set('repository_channels', repository_channels)
sublime.save_settings('Package Control.sublime-settings')
sublime.status_message(('Channel %s successfully ' +
'added') % input)

def on_change(self, input):
pass

def on_cancel(self):
pass
36 changes: 36 additions & 0 deletions package_control/commands/add_repository_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import sublime
import sublime_plugin


class AddRepositoryCommand(sublime_plugin.WindowCommand):
"""
A command to add a new repository to the user's machine
"""

def run(self):
self.window.show_input_panel('GitHub or BitBucket Web URL, or Custom' +
' JSON Repository URL', '', self.on_done,
self.on_change, self.on_cancel)

def on_done(self, input):
"""
Input panel handler - adds the provided URL as a repository
:param input:
A string of the URL to the new repository
"""

settings = sublime.load_settings('Package Control.sublime-settings')
repositories = settings.get('repositories', [])
if not repositories:
repositories = []
repositories.append(input)
settings.set('repositories', repositories)
sublime.save_settings('Package Control.sublime-settings')
sublime.status_message('Repository %s successfully added' % input)

def on_change(self, input):
pass

def on_cancel(self):
pass
35 changes: 35 additions & 0 deletions package_control/commands/create_binary_package_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import sublime_plugin

from ..package_creator import PackageCreator


class CreateBinaryPackageCommand(sublime_plugin.WindowCommand, PackageCreator):
"""
Command to create a binary .sublime-package file. Binary packages in
general exclude the .py source files and instead include the .pyc files.
Actual included and excluded files are controlled by settings.
"""

def run(self):
self.show_panel()

def on_done(self, picked):
"""
Quick panel user selection handler - processes the user package
selection and create the package file
:param picked:
An integer of the 0-based package name index from the presented
list. -1 means the user cancelled.
"""

if picked == -1:
return
package_name = self.packages[picked]
package_destination = self.get_package_destination()

if self.manager.create_package(package_name, package_destination,
binary_package=True):
self.window.run_command('open_dir', {"dir":
package_destination, "file": package_name +
'.sublime-package'})
Loading

0 comments on commit e509241

Please sign in to comment.