diff --git a/aqt/main.py b/aqt/main.py index 4745eb789..f79d7891f 100755 --- a/aqt/main.py +++ b/aqt/main.py @@ -36,7 +36,7 @@ class AnkiQt(QMainWindow): self.setup() splash.update() # load plugins - self.loadPlugins() + self.setupPlugins() splash.update() # show main window splash.finish(self) @@ -334,6 +334,10 @@ counts are %d %d %d if self.appUpdated: self.config['version'] = aqt.appVersion + def setupPlugins(self): + import aqt.plugins + self.pluginManager = aqt.plugins.PluginManager(self) + def setupThreads(self): self._mainThread = QThread.currentThread() @@ -1711,9 +1715,6 @@ This deck already exists on your computer. Overwrite the local copy?"""), self.connect(m.actionDownloadMissingMedia, s, self.onDownloadMissingMedia) self.connect(m.actionLocalizeMedia, s, self.onLocalizeMedia) self.connect(m.actionCram, s, self.onCram) - self.connect(m.actionOpenPluginFolder, s, self.onOpenPluginFolder) - self.connect(m.actionEnableAllPlugins, s, self.onEnableAllPlugins) - self.connect(m.actionDisableAllPlugins, s, self.onDisableAllPlugins) self.connect(m.actionStudyOptions, s, self.onStudyOptions) self.connect(m.actionDonate, s, self.onDonate) self.connect(m.actionRecordNoiseProfile, s, self.onRecordNoiseProfile) @@ -1833,172 +1834,6 @@ This deck already exists on your computer. Overwrite the local copy?"""), _(" Please ensure it is set correctly and then restart Anki.") ) - # Plugins - ########################################################################## - - def pluginsFolder(self): - dir = self.config.confDir - if sys.platform.startswith("win32"): - dir = dir.encode(sys.getfilesystemencoding()) - return os.path.join(dir, "plugins") - - def loadPlugins(self): - if sys.platform.startswith("win32"): - self.clearPluginCache() - self.disableObsoletePlugins() - plugdir = self.pluginsFolder() - sys.path.insert(0, plugdir) - plugins = self.enabledPlugins() - plugins.sort() - self.registeredPlugins = {} - for plugin in plugins: - try: - nopy = plugin.replace(".py", "") - __import__(nopy) - except: - print "Error in %s" % plugin - traceback.print_exc() - self.checkForUpdatedPlugins() - self.disableCardMenuItems() - self.rebuildPluginsMenu() - # run the obsolete init hook - try: - runHook('init') - except: - showWarning( - _("Broken plugin:\n\n%s") % - unicode(traceback.format_exc(), "utf-8", "replace")) - - def clearPluginCache(self): - "Clear .pyc files which may cause crashes if Python version updated." - dir = self.pluginsFolder() - 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 disableObsoletePlugins(self): - dir = self.pluginsFolder() - native = _( - "The %s plugin has been disabled, as Anki supports "+ - "this natively now.") - plugins = [ - ("Custom Media Directory.py", - (native % "custom media folder") + _(""" \ -Please visit Settings>Preferences.""")), - ("Regenerate Reading Field.py", _("""\ -The regenerate reading field plugin has been disabled, as the Japanese \ -support plugin supports this now. Please download the latest version.""")), - ("Sync LaTeX with iPhone client.py", - native % "sync LaTeX"), - ("Incremental Reading.py", - _("""The incremental reading plugin has been disabled because \ -it needs updates.""")), - ("Learn Mode.py", _("""\ -The learn mode plugin has been disabled because it needs to be rewritten \ -to work with this version of Anki.""")) - ] - for p in plugins: - path = os.path.join(dir, p[0]) - if os.path.exists(path): - new = path.replace(".py", ".disabled") - if os.path.exists(new): - os.unlink(new) - os.rename(path, new) - showInfo(p[1]) - - def rebuildPluginsMenu(self): - if getattr(self, "pluginActions", None) is None: - self.pluginActions = [] - for action in self.pluginActions: - self.form.menuStartup.removeAction(action) - all = self.allPlugins() - all.sort() - for fname in all: - enabled = fname.endswith(".py") - p = re.sub("\.py(\.off)?", "", fname) - if p+".py" in self.registeredPlugins: - p = self.registeredPlugins[p+".py"]['name'] - a = QAction(p, self) - a.setCheckable(True) - a.setChecked(enabled) - self.connect(a, SIGNAL("triggered()"), - lambda fname=fname: self.togglePlugin(fname)) - self.form.menuStartup.addAction(a) - self.pluginActions.append(a) - - def enabledPlugins(self): - return [p for p in os.listdir(self.pluginsFolder()) - if p.endswith(".py")] - - def disabledPlugins(self): - return [p for p in os.listdir(self.pluginsFolder()) - if p.endswith(".py.off")] - - def allPlugins(self): - return [p for p in os.listdir(self.pluginsFolder()) - if p.endswith(".py.off") or p.endswith(".py")] - - def onOpenPluginFolder(self, path=None): - if path is None: - path = self.pluginsFolder() - if sys.platform == "win32": - if isinstance(path, unicode): - path = path.encode(sys.getfilesystemencoding()) - anki.utils.call(["explorer", path], wait=False) - else: - QDesktopServices.openUrl(QUrl("file://" + path)) - - def onGetPlugins(self): - QDesktopServices.openUrl(QUrl("http://ichi2.net/anki/wiki/Plugins")) - - def enablePlugin(self, p): - pd = self.pluginsFolder() - old = os.path.join(pd, p) - new = os.path.join(pd, p.replace(".off", "")) - try: - os.unlink(new) - except: - pass - os.rename(old, new) - - def disablePlugin(self, p): - pd = self.pluginsFolder() - old = os.path.join(pd, p) - new = os.path.join(pd, p.replace(".py", ".py.off")) - try: - os.unlink(new) - except: - pass - os.rename(old, new) - - def onEnableAllPlugins(self): - for p in self.disabledPlugins(): - self.enablePlugin(p) - self.rebuildPluginsMenu() - - def onDisableAllPlugins(self): - for p in self.enabledPlugins(): - self.disablePlugin(p) - self.rebuildPluginsMenu() - - def togglePlugin(self, plugin): - if plugin.endswith(".py"): - self.disablePlugin(plugin) - else: - self.enablePlugin(plugin) - self.rebuildPluginsMenu() - - def registerPlugin(self, name, updateId): - return - # src = os.path.basename(inspect.getfile(inspect.currentframe(1))) - # self.registeredPlugins[src] = {'name': name, - # 'id': updateId} - - def checkForUpdatedPlugins(self): - pass - # Custom styles ########################################################################## diff --git a/aqt/plugins.py b/aqt/plugins.py new file mode 100644 index 000000000..b02afddcf --- /dev/null +++ b/aqt/plugins.py @@ -0,0 +1,167 @@ +# Copyright: Damien Elmes +# -*- coding: utf-8 -*- +# License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html + +import sys, os, re +from PyQt4.QtCore import * +from PyQt4.QtGui import * +from aqt.utils import showInfo, showWarning, openFolder +from anki.hooks import runHook + +class PluginManager(object): + + def __init__(self, mw): + self.mw = mw + f = self.mw.form; s = SIGNAL("triggered()") + self.mw.connect(f.actionOpenPluginFolder, s, self.onOpenPluginFolder) + self.mw.connect(f.actionEnableAllPlugins, s, self.onEnableAllPlugins) + self.mw.connect(f.actionDisableAllPlugins, s, self.onDisableAllPlugins) + if sys.platform.startswith("win32"): + self.clearPluginCache() + self.disableObsoletePlugins() + plugdir = self.pluginsFolder() + sys.path.insert(0, plugdir) + plugins = self.enabledPlugins() + plugins.sort() + self.registeredPlugins = {} + for plugin in plugins: + try: + nopy = plugin.replace(".py", "") + __import__(nopy) + except: + print "Error in %s" % plugin + traceback.print_exc() + self.mw.disableCardMenuItems() + self.rebuildPluginsMenu() + # run the obsolete init hook + try: + runHook('init') + except: + showWarning( + _("Broken plugin:\n\n%s") % + unicode(traceback.format_exc(), "utf-8", "replace")) + + def pluginsFolder(self): + dir = self.mw.config.confDir + if sys.platform.startswith("win32"): + dir = dir.encode(sys.getfilesystemencoding()) + return os.path.join(dir, "plugins") + + def clearPluginCache(self): + "Clear .pyc files which may cause crashes if Python version updated." + dir = self.pluginsFolder() + 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 disableObsoletePlugins(self): + dir = self.pluginsFolder() + native = _( + "The %s plugin has been disabled, as Anki supports "+ + "this natively now.") + plugins = [ + ("Custom Media Directory.py", + (native % "custom media folder") + _(""" \ +Please visit Settings>Preferences.""")), + ("Regenerate Reading Field.py", _("""\ +The regenerate reading field plugin has been disabled, as the Japanese \ +support plugin supports this now. Please download the latest version.""")), + ("Sync LaTeX with iPhone client.py", + native % "sync LaTeX"), + ("Incremental Reading.py", + _("""The incremental reading plugin has been disabled because \ +it needs updates.""")), + ("Learn Mode.py", _("""\ +The learn mode plugin has been disabled because it needs to be rewritten \ +to work with this version of Anki.""")) + ] + for p in plugins: + path = os.path.join(dir, p[0]) + if os.path.exists(path): + new = path.replace(".py", ".disabled") + if os.path.exists(new): + os.unlink(new) + os.rename(path, new) + showInfo(p[1]) + + def rebuildPluginsMenu(self): + if getattr(self, "pluginActions", None) is None: + self.pluginActions = [] + for action in self.pluginActions: + self.mw.form.menuStartup.removeAction(action) + all = self.allPlugins() + all.sort() + for fname in all: + enabled = fname.endswith(".py") + p = re.sub("\.py(\.off)?", "", fname) + if p+".py" in self.registeredPlugins: + p = self.registeredPlugins[p+".py"]['name'] + a = QAction(p, self.mw) + a.setCheckable(True) + a.setChecked(enabled) + self.mw.connect(a, SIGNAL("triggered()"), + lambda fname=fname: self.togglePlugin(fname)) + self.mw.form.menuStartup.addAction(a) + self.pluginActions.append(a) + + def enabledPlugins(self): + return [p for p in os.listdir(self.pluginsFolder()) + if p.endswith(".py")] + + def disabledPlugins(self): + return [p for p in os.listdir(self.pluginsFolder()) + if p.endswith(".py.off")] + + def allPlugins(self): + return [p for p in os.listdir(self.pluginsFolder()) + if p.endswith(".py.off") or p.endswith(".py")] + + def onOpenPluginFolder(self, path=None): + if path is None: + path = self.pluginsFolder() + openFolder(path) + + def onGetPlugins(self): + QDesktopServices.openUrl(QUrl("http://ichi2.net/anki/wiki/Plugins")) + + def enablePlugin(self, p): + pd = self.pluginsFolder() + old = os.path.join(pd, p) + new = os.path.join(pd, p.replace(".off", "")) + try: + os.unlink(new) + except: + pass + os.rename(old, new) + + def disablePlugin(self, p): + pd = self.pluginsFolder() + old = os.path.join(pd, p) + new = os.path.join(pd, p.replace(".py", ".py.off")) + try: + os.unlink(new) + except: + pass + os.rename(old, new) + + def onEnableAllPlugins(self): + for p in self.disabledPlugins(): + self.enablePlugin(p) + self.rebuildPluginsMenu() + + def onDisableAllPlugins(self): + for p in self.enabledPlugins(): + self.disablePlugin(p) + self.rebuildPluginsMenu() + + def togglePlugin(self, plugin): + if plugin.endswith(".py"): + self.disablePlugin(plugin) + else: + self.enablePlugin(plugin) + self.rebuildPluginsMenu() + + def registerPlugin(self, name, updateId): + return diff --git a/aqt/utils.py b/aqt/utils.py index 7fbe69fae..13002b130 100644 --- a/aqt/utils.py +++ b/aqt/utils.py @@ -3,11 +3,10 @@ from PyQt4.QtGui import * from PyQt4.QtCore import * - -from anki.sound import playFromText, stripSounds - import re, os, sys, urllib, time import aqt +from anki.sound import playFromText, stripSounds +from anki.utils import call def openLink(link): QDesktopServices.openUrl(QUrl(link)) @@ -285,3 +284,11 @@ def getBase(deck, card): return '' % base else: return "" + +def openFolder(path): + if sys.platform == "win32": + if isinstance(path, unicode): + path = path.encode(sys.getfilesystemencoding()) + call(["explorer", path], wait=False) + else: + QDesktopServices.openUrl(QUrl("file://" + path))