Anki/aqt/addons.py
2012-04-22 05:30:46 +09:00

171 lines
5.4 KiB
Python

# Copyright: Damien Elmes <anki@ichi2.net>
# -*- coding: utf-8 -*-
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import sys, os, re, traceback, time
from cStringIO import StringIO
from aqt.qt import *
from aqt.utils import showInfo, showWarning, openFolder, isWin, openLink
from anki.hooks import runHook, addHook, remHook
from aqt.webview import AnkiWebView
from zipfile import ZipFile
import aqt.forms
import aqt
from anki.sync import httpCon
import aqt.sync # monkey-patches httplib2
class AddonManager(object):
def __init__(self, mw):
self.mw = mw
f = self.mw.form; s = SIGNAL("triggered()")
self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenAddonFolder)
self.mw.connect(f.actionDownloadSharedPlugin, s, self.onGetAddons)
if isWin:
self.clearAddonCache()
sys.path.insert(0, self.addonsFolder())
self.loadAddons()
def files(self):
return [f for f in os.listdir(self.addonsFolder())
if f.endswith(".py")]
def loadAddons(self):
for file in self.files():
try:
__import__(file.replace(".py", ""))
except:
traceback.print_exc()
# Menus
######################################################################
def onOpenAddonFolder(self, path=None):
if path is None:
path = self.addonsFolder()
openFolder(path)
# Tools
######################################################################
def addonsFolder(self):
dir = self.mw.pm.addonFolder()
if isWin:
dir = dir.encode(sys.getfilesystemencoding())
return dir
def clearAddonCache(self):
"Clear .pyc files which may cause crashes if Python version updated."
dir = self.addonsFolder()
for curdir, dirs, files in os.walk(dir):
for f in files:
if not f.endswith(".pyc"):
continue
os.unlink(os.path.join(curdir, f))
def registerAddon(self, name, updateId):
# not currently used
return
# Installing add-ons
######################################################################
def onGetAddons(self):
GetAddons(self.mw)
def install(self, data, fname):
if fname.endswith(".py"):
# .py files go directly into the addon folder
path = os.path.join(self.addonsFolder(), fname)
open(path, "w").write(data)
return
# .zip file
z = ZipFile(StringIO(data))
base = self.addonsFolder()
for n in z.namelist():
if n.endswith("/"):
# folder; ignore
continue
# write
z.extract(n, base)
class GetAddons(QDialog):
def __init__(self, mw):
QDialog.__init__(self, mw)
self.mw = mw
self.form = aqt.forms.getaddons.Ui_Dialog()
self.form.setupUi(self)
b = self.form.buttonBox.addButton(
_("Browse"), QDialogButtonBox.ActionRole)
self.connect(b, SIGNAL("clicked()"), self.onBrowse)
self.exec_()
def onBrowse(self):
openLink(aqt.appShared + "addons/")
def accept(self):
try:
code = int(self.form.code.text())
except ValueError:
showWarning(_("Invalid code."))
return
QDialog.accept(self)
# create downloader thread
self.thread = AddonDownloader(code)
self.connect(self.thread, SIGNAL("recv"), self.onRecv)
self.recvBytes = 0
self.thread.start()
self.mw.progress.start(immediate=True)
while not self.thread.isFinished():
self.mw.app.processEvents()
self.thread.wait(100)
if not self.thread.error:
# success
self.mw.addonManager.install(self.thread.data, self.thread.fname)
self.mw.progress.finish()
showInfo(_("Download successful. Please restart Anki."))
else:
self.mw.progress.finish()
showWarning(_("Download failed: %s") % self.thread.error)
def onRecv(self, total):
self.mw.progress.update(label="%dKB downloaded" % (total/1024))
class AddonDownloader(QThread):
def __init__(self, code):
QThread.__init__(self)
self.code = code
def run(self):
# setup progress handler
self.byteUpdate = time.time()
self.recvTotal = 0
def canPost():
if (time.time() - self.byteUpdate) > 0.1:
self.byteUpdate = time.time()
return True
def recvEvent(bytes):
self.recvTotal += bytes
if canPost():
self.emit(SIGNAL("recv"), self.recvTotal)
addHook("httpRecv", recvEvent)
con = httpCon()
try:
resp, cont = con.request(
aqt.appShared + "download/%d" % self.code)
except Exception, e:
self.error = unicode(e)
return
finally:
remHook("httpRecv", recvEvent)
if resp['status'] == '200':
self.error = None
self.fname = re.match("attachment; filename=(.+)",
resp['content-disposition']).group(1)
self.data = cont
elif resp['status'] == '403':
self.error = _("Invalid code.")
else:
self.error = _("Error downloading: %s") % resp['status']