diff --git a/src/cryptobox/core/main.py b/src/cryptobox/core/main.py index f0f4a60..8bdebde 100644 --- a/src/cryptobox/core/main.py +++ b/src/cryptobox/core/main.py @@ -203,21 +203,6 @@ class CryptoBoxProps(CryptoBox): return None - def getAvailableLanguages(self): - '''reads all files in path LangDir and returns a list of - basenames from existing hdf files, that should are all available - languages''' - # TODO: for now we hardcode it - change this! - return "en fr si de".split(" ") - # TODO: old implementation (before gettext) - remove it - languages = [ f.rstrip(".hdf") - for f in os.listdir(self.prefs["Locations"]["LangDir"]) - if f.endswith(".hdf") ] - if len(languages) < 1: - self.log.error("No .hdf files found! The website won't render properly.") - return languages - - def sendEventNotification(self, event, event_infos): """call all available scripts in the event directory with some event information""" event_dir = self.prefs["Locations"]["EventDir"] diff --git a/src/cryptobox/core/settings.py b/src/cryptobox/core/settings.py index 0bf8b97..918a665 100644 --- a/src/cryptobox/core/settings.py +++ b/src/cryptobox/core/settings.py @@ -355,7 +355,7 @@ DefaultVolumePrefix = string(min=1) DefaultCipher = string(default="aes-cbc-essiv:sha256") ConfigVolumeLabel = string(min=1, default="cbox_config") UseConfigPartition = integer(min=0, max=1, default=0) -DisabledPlugins = list(default=[]) +DisabledPlugins = list(default=list()) [Locations] MountParentDir = directoryExists(default="/var/cache/cryptobox-server/mnt") @@ -373,7 +373,7 @@ Details = string(min=1) [WebSettings] Stylesheet = string(min=1) -Language = string(min=1, default="en") +Languages = list(min=1,default=list("en")) [Programs] cryptsetup = fileExecutable(default="/sbin/cryptsetup") diff --git a/src/cryptobox/plugins/base.py b/src/cryptobox/plugins/base.py index 7c9e1cf..d4916ea 100644 --- a/src/cryptobox/plugins/base.py +++ b/src/cryptobox/plugins/base.py @@ -29,11 +29,12 @@ class CryptoBoxPlugin: fallbackIconFileName = "plugin_icon_unknown.gif" - def __init__(self, cbox, pluginDir): + def __init__(self, cbox, pluginDir, siteClass=None): self.cbox = cbox self.hdf = {} self.pluginDir = pluginDir self.hdf_prefix = "Data.Plugins.%s." % self.getName() + self.site = siteClass diff --git a/src/cryptobox/plugins/manage.py b/src/cryptobox/plugins/manage.py index 54b7757..529daf7 100644 --- a/src/cryptobox/plugins/manage.py +++ b/src/cryptobox/plugins/manage.py @@ -8,9 +8,10 @@ import logging class PluginManager: """manage available plugins""" - def __init__(self, cbox, plugin_dirs="."): + def __init__(self, cbox, plugin_dirs=".", siteClass=None): self.cbox = cbox self.log = logging.getLogger("CryptoBox") + self.site = siteClass if hasattr(plugin_dirs, "__iter__"): self.plugin_dirs = [os.path.abspath(dir) for dir in plugin_dirs] else: @@ -43,7 +44,7 @@ class PluginManager: pl_class = getattr(imp.load_source(name, plfile), name) except AttributeError: return None - return pl_class(self.cbox, os.path.dirname(plfile)) + return pl_class(self.cbox, os.path.dirname(plfile), self.site) else: return None diff --git a/src/cryptobox/web/dataset.py b/src/cryptobox/web/dataset.py index feadfa9..59d8375 100644 --- a/src/cryptobox/web/dataset.py +++ b/src/cryptobox/web/dataset.py @@ -22,13 +22,19 @@ class WebInterfaceDataset(dict): def setCryptoBoxState(self): import cherrypy import cryptobox.core.main + import cryptobox.web.languages self["Data.Version"] = cryptobox.core.main.VERSION - langs = self.cbox.getAvailableLanguages() + langs = self.cbox.prefs["WebSettings"]["Languages"] langs.sort() for (index, lang) in enumerate(langs): - self.cbox.log.info("language loaded: %s" % lang) - self["Data.Languages.%d.name" % index] = lang - self["Data.Languages.%d.link" % index] = self.__getLanguageName(lang) + try: + (langname, plural_info) = cryptobox.web.languages.LANGUAGE_INFO[lang] + self["Data.Languages.%d.link" % index] = langname + self["Data.Languages.%d.name" % index] = lang + self.cbox.log.info("language loaded: %s" % lang) + except KeyError: + ## language was not found + self.cbox.log.warn("invalid language specified in configuration: %s" % lang) try: self["Data.ScriptURL.Prot"] = cherrypy.request.scheme host = cherrypy.request.headers["Host"] @@ -122,7 +128,7 @@ class WebInterfaceDataset(dict): self["Settings.LanguageDir"] = os.path.abspath(self.prefs["Locations"]["LangDir"]) self["Settings.DocDir"] = os.path.abspath(self.prefs["Locations"]["DocDir"]) self["Settings.Stylesheet"] = self.prefs["WebSettings"]["Stylesheet"] - self["Settings.Language"] = self.prefs["WebSettings"]["Language"] + self["Settings.Language"] = self.prefs["WebSettings"]["Languages"][0] self["Settings.PluginDir"] = self.prefs["Locations"]["PluginDir"] self["Settings.SettingsDir"] = self.prefs["Locations"]["SettingsDir"] diff --git a/src/cryptobox/web/languages.py b/src/cryptobox/web/languages.py new file mode 100644 index 0000000..ec70453 --- /dev/null +++ b/src/cryptobox/web/languages.py @@ -0,0 +1,23 @@ +"""supply information about existing languages +""" + +## every language information should contain (name, pluralformat) +LANGUAGE_INFO = { + "cs": ('Český', ('3', '(n==1) ? 0 : (n>=2 && n< =4) ? 1 : 2')), + "da": ('Dansk', ('2', '(n != 1)')), + "de": ('Deutsch', ('2', '(n != 1)')), + "en": ('English', ('2', '(n != 1)')), + "es": ('Español', ('2', '(n != 1)')), + "fi": ('Suomi', ('2', '(n != 1)')), + "fr": ('Français', ('2', '(n != 1)')), + "hu": ('Magyar', ('1', '0')), + "it": ('Italiano', ('2', '(n != 1)')), + "ja": ('日本語', ('1', '0')), + "nl": ('Nederlands', ('2', '(n != 1)')), + "pl": ('Polski', ('3', '(n==1 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)')), + "pt": ('Português', ('2', '(n != 1)')), + "ru": ('Русский', ('3', '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10< =4 && (n%100<10 || n%100>=20) ? 1 : 2)')), + "sl": ('Slovensko', ('4', '(n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3)')), + "sv": ('Svenska', ('2', '(n != 1)')), + } + diff --git a/src/cryptobox/web/sites.py b/src/cryptobox/web/sites.py index 9549c6c..32c2151 100644 --- a/src/cryptobox/web/sites.py +++ b/src/cryptobox/web/sites.py @@ -46,6 +46,8 @@ class WebInterfaceSites: self.__resetDataset() ## store the original http error handler self._cp_on_http_error = self.newHTTPErrorHandler + ## set initial language order + self.langOrder = self.cbox.prefs["WebSettings"]["Languages"] def __resetDataset(self): @@ -64,7 +66,7 @@ class WebInterfaceSites: def __loadPlugins(self): - self.pluginList = cryptobox.plugins.manage.PluginManager(self.cbox, self.prefs["Locations"]["PluginDir"]) + self.pluginList = cryptobox.plugins.manage.PluginManager(self.cbox, self.prefs["Locations"]["PluginDir"], self) for plugin in self.pluginList.getPlugins(): if not plugin: continue plname = plugin.getName() @@ -134,6 +136,14 @@ class WebInterfaceSites: return self.return_plugin_action(self.pluginList.getPlugin("disks"))(**param_dict) def newHTTPErrorHandler(self, errorCode, message): + """handle http errors gracefully + + 404 - not found errors: ignored if url is below /cryptobox-misc/ + other 404 errors: send the error code and return a nice informative page + 500 - runtime errors: return "ok" exit code and show a polite excuse + others: are there any other possible http errors? + """ + import traceback, sys ## we ignore uninteresting not-found errors if (errorCode == 404) and \ (cherrypy.request.path.startswith("/cryptobox-misc/") or \ @@ -151,8 +161,10 @@ class WebInterfaceSites: if errorCode == 500: ## we fix the error code (200 is "OK") cherrypy.response.status = 200 - ## TODO: 'message' is None - we should check a stack trace or something? - self.cbox.log.warn("HTTP-ERROR[500] - a runtime error occoured: %s" % str(message)) + self.cbox.log.error("HTTP-ERROR[500] - a runtime error occoured: %s" % str(message)) + ## add a traceback and exception information to the lo + for a in traceback.format_exception(*sys.exc_info()): + self.cbox.log.error("\t%s" % a) self.dataset["Data.Warning"] = "RuntimeError" cherrypy.response.body = self.__render("empty") return @@ -285,7 +297,8 @@ class WebInterfaceSites: examples are: non-https, readonly-config, ... """ - ## TODO: maybe add an option "mount" (if it is available, but inactive)? + ## this check is done _after_ "resetDataSet" -> a possible config partition was + ## loaded before if self.cbox.prefs.requiresPartition() and not self.cbox.prefs.getActivePartition(): self.dataset["Data.EnvironmentWarning"] = "ReadOnlyConfig" # TODO: turn this on soon (add "not") - for now it is annoying @@ -311,31 +324,28 @@ class WebInterfaceSites: def __setWebLang(self, value): - guess = value - availLangs = self.cbox.getAvailableLanguages() - ## no language specified: check browser language - if not guess: - guess = self.__getPreferredBrowserLanguage(availLangs) - ## no preferred language or invalid language? - if not guess \ - or not guess in availLangs \ - or re.search(u'\W', guess): - ## warn only for invalid languages - if not guess is None: - self.cbox.log.info("invalid language choosen: %s" % guess) - guess = self.prefs["WebSettings"]["Language"] - ## maybe the language is still not valid - if not guess in availLangs: - self.log.warn("the configured language is invalid: %s" % guess) - guess = "en" - ## maybe there is no english dataset??? - if not guess in availLangs: - self.log.warn("couldn't find the english dataset") - guess = availLangs[0] - self.dataset["Settings.Language"] = guess - ## we only have to save it, if it was specified correctly and explicitly - if value == guess: - self.dataset["Settings.LinkAttrs.weblang"] = guess + """set the preferred priority of languages according to the following order: + 1. language selected via web interface + 2. preferred browser language setting + 3. languages defined in the config file + """ + ## start with the configured language order + langOrder = self.cbox.prefs["WebSettings"]["Languages"][:] + ## put the preferred browser language in front + guess = self.__getPreferredBrowserLanguage(langOrder) + if guess: + langOrder.remove(guess) + langOrder.insert(0,guess) + ## is the chosen language (via web interface) valid? - put it in front + if value and (value in langOrder) and (not re.search(u'\W',value)): + langOrder.remove(value) + langOrder.insert(0,value) + elif value: + self.cbox.log.info("invalid language selected: %s" % value) + ## store current language setting + self.langOrder = langOrder + self.dataset["Settings.Language"] = langOrder[0] + self.dataset["Settings.LinkAttrs.weblang"] = langOrder[0] def __getPreferredBrowserLanguage(self, availLangs): @@ -366,6 +376,8 @@ class WebInterfaceSites: def __setDevice(self, device): + """check a device name that was chosen via the web interface + issue a warning if the device is invalid""" if device and re.match(u'[\w /\-]+$', device) and self.cbox.getContainer(device): self.log.debug("select device: %s" % device) return True @@ -375,19 +387,10 @@ class WebInterfaceSites: return False - def __checkVolumeName(self, name): - if name and re.match(u'[\w \-]+$', name): - return True - else: - return False - - - def __getLanguageValue(self, value): - hdf = self.__getLanguageData(self.dataset["Settings.Language"]) - return hdf.getValue(value, "") - - def __substituteGettext(self, languages, textDomain, hdf): + """substitute all texts in the hdf dataset with their translated + counterparts as returned by gettext + """ import gettext try: translator = gettext.translation(textDomain, languages=languages) @@ -407,41 +410,32 @@ class WebInterfaceSites: walk_tree(hdf) - def __getLanguageData(self, web_lang="en"): + def __getLanguageData(self): + """return the hdf dataset of the main interface and all plugins + translations are done according to self.langOrder + """ + ## check if the language setting was changed - use cached data if possible + try: + if self.cachedLanguageData["langOrder"] == self.langOrder: + self.cbox.log.debug("using cached language data") + return self.cachedLanguageData["hdf"] + except AttributeError: + pass + self.cbox.log.debug("generating language data") hdf = neo_util.HDF() hdf.readFile(os.path.join(self.prefs["Locations"]["TemplateDir"],"language.hdf")) - self.__substituteGettext([web_lang], GETTEXT_DOMAIN, hdf) + self.__substituteGettext(self.langOrder, GETTEXT_DOMAIN, hdf) ## load the language data of all plugins for p in self.pluginList.getPlugins(): pl_lang = p.getLanguageData() - self.__substituteGettext([web_lang], "%s-feature-%s" % (GETTEXT_DOMAIN, p.getName()), pl_lang) + self.__substituteGettext(self.langOrder, "%s-feature-%s" % (GETTEXT_DOMAIN, p.getName()), pl_lang) hdf.copy("Plugins.%s" % p.getName(), pl_lang) self.cbox.log.debug("language data for plugin loaded: %s" % p.getName()) + ## cache result for later retrieval + self.cachedLanguageData = {"langOrder": self.langOrder, "hdf": hdf} return hdf - def __getLanguageData2(self, web_lang="en"): - default_lang = "en" - conf_lang = self.prefs["WebSettings"]["Language"] - hdf = neo_util.HDF() - langDir = os.path.abspath(self.prefs["Locations"]["LangDir"]) - langFiles = [] - ## first: read default language (en) - if (default_lang != conf_lang) and (default_lang != web_lang): - langFiles.append(os.path.join(langDir, default_lang + ".hdf")) - ## second: read language as defined in the config file - if (conf_lang != web_lang): - langFiles.append(os.path.join(langDir, conf_lang + ".hdf")) - ## third: read language as configured via web interface - langFiles.append(os.path.join(langDir, web_lang + ".hdf")) - for langFile in langFiles: - if os.access(langFile, os.R_OK): - hdf.readFile(langFile) - else: - log.warn("Couldn't read language file: %s" % langFile) - return hdf - - def __render(self, renderInfo, plugin=None): '''renders from clearsilver templates and returns the resulting html ''' @@ -457,7 +451,7 @@ class WebInterfaceSites: ## load the language data hdf = neo_util.HDF() - hdf.copy("Lang", self.__getLanguageData(self.dataset["Settings.Language"])) + hdf.copy("Lang", self.__getLanguageData()) ## first: assume, that the template file is in the global template directory self.dataset["Settings.TemplateFile"] = os.path.abspath(os.path.join(self.prefs["Locations"]["TemplateDir"], template + ".cs"))