Source

PackageManager / package_manager.py

Full commit
import csv
import datetime
import json
import os
import Queue
import re
import shutil
import tempfile
import threading
import urllib
import urlparse
import zipfile

import sublime

import db


print_lock = threading.Lock()
GH_DATE = "%Y-%m-%dT%H:%M:%SZ"
DOING_WORK = False


class LocalPackage(object):
    # XXX Sort this out - db should return instances of this.
    # XXX Make it work with Bitbucket too, and possibly others.
    """I represent a package. I can be invalid, which means that I've been
    requested but there is no information about me. Always check whether I'm
    invalid before using me and discard me if needed.
    """
    def __init__(self, entry):
        self.invalid = True
        info = db.fetch_by_any_field(entry)
        if info:
            self.url = info[0]
            self.name = info[1]
            self.last_updated = info[2]
            self.invalid = False
            
    def is_installed(self):
        loc = os.path.join(sublime.installed_packages_path(), self.name + '.sublime-package')
        return os.path.exists(loc)
        
    def is_current(self):
        data = get_resource_path(self.url)
        user, item = os.path.split(data)        

        info = urllib.urlopen("https://api.github.com/repos/%s/%s" % (user, item))
        json_data = json.loads(info.read())
        
        last_push = json_data['pushed_at']

        return datetime.datetime.strptime(last_push, GH_DATE) <= \
                                datetime.datetime.strptime(self.last_updated, GH_DATE)


def get_resource_path(url):
    return urlparse.urlsplit(url).path[1:]


def downloader(q):
    global DOING_WORK
    DOING_WORK = True
    while True:
        try:
            url = q.get(timeout=2)
        except Queue.Empty:
            break
        download_package(url)
    
    with print_lock:
        print "[PackageManager]:out: Finished downloading packages."
    DOING_WORK = False


# XXX Make a OnlineResource capable of downloading itself?
def download_package(url):
    lp = LocalPackage(url)
    with print_lock:
        print "[PackageManager]:out: Checking package status (%s)..." % url
    if not lp.invalid and lp.is_installed() and lp.is_current():
        with print_lock:
            print "[PackageManager]:out: The %s package is up-to-date." % lp.name
        return
    
    data = get_resource_path(url)
    user, item = os.path.split(data)

    with print_lock:
        print "[PackageManager]:out: Downloading bits for %s..." % item
    fh = urllib.urlopen('https://github.com/%s/%s/zipball/master' % (user, item))
    
    build_area = tempfile.mkdtemp()
    with open(os.path.join(build_area, 'temp.zip'), 'wb') as f:
        f.write(fh.read())

    with print_lock:
        print "[PackageManager]:out: Building %s.sublime-package..." % item
    zf = zipfile.ZipFile(os.path.join(build_area, 'temp.zip'))
    out = zf.infolist()[0].filename
    zf.extractall(os.path.join(build_area, 'build'))
    zf.close()

    zo = zipfile.ZipFile(os.path.join(build_area, "build/%s.sublime-package" % item), 'a')
    old_cd = os.getcwd()
    os.chdir(os.path.join(build_area, "build/%s" % out))
    for a, b, c in os.walk('.'):
        for x in c:
            zo.write(os.path.join(a, x))
    zo.close()
    os.chdir(old_cd)

    with print_lock:
        print "[PackageManager]:out: Updating local package database..."
    #get package data
    info = urllib.urlopen("https://api.github.com/repos/%s/%s" % (user, item))
    json_data = json.loads(info.read())
    
    to_store = url, item, json_data['pushed_at'], 'CURRENT' + '\n'

    with db.open_db(mode='a') as f:
        f.write("\t".join(to_store))

    with print_lock:
        print "[PackageManager]:out: Installing package: %s..." % item

    # remove existing backups
    if not os.path.exists(sublime.installed_packages_path()):
        shutil.mkdir(sublime.installed_packages_path())
    now = os.path.join(sublime.installed_packages_path(), "%s.sublime-package" % item)
    if os.path.exists(now):
        os.unlink(now)
    
    # move package to destination
    shutil.move(os.path.join(build_area, 'build/%s.sublime-package' % item),
                            os.path.join(
                                        sublime.installed_packages_path(),
                                        "%s.sublime-package" % item))

    # clean up
    shutil.rmtree(build_area)

    with print_lock:
        print "[PackageManager]:out: Finished installing package: %s." % item


def start_download(urls=[]):
    if DOING_WORK:
        with print_lock:
            print "[PackageManager]:out: Processing other request at the moment."
            return

    q = Queue.Queue()
    for url in urls:
        q.put(url)
    
    t = threading.Thread(target=downloader, args=(q,))
    t.start()