From 4830d602692d4724650c6f90f9d088cfefd4dc46 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?=
Date: Wed, 4 May 2016 13:28:28 +0200
Subject: [PATCH] Announcements: Ensure validity of channel keys, made
refreshable
---
.../plugins/announcements/__init__.py | 119 +++++++++++++-----
.../announcements/static/js/announcements.js | 4 +
.../templates/announcements_settings.jinja2 | 2 +-
.../translations/de/LC_MESSAGES/messages.mo | Bin 63278 -> 63280 bytes
.../translations/de/LC_MESSAGES/messages.po | 27 ++--
translations/de/LC_MESSAGES/messages.mo | Bin 63278 -> 63280 bytes
translations/de/LC_MESSAGES/messages.po | 27 ++--
translations/messages.pot | 14 +--
8 files changed, 129 insertions(+), 64 deletions(-)
diff --git a/src/octoprint/plugins/announcements/__init__.py b/src/octoprint/plugins/announcements/__init__.py
index 366f5130..d7b1fa67 100644
--- a/src/octoprint/plugins/announcements/__init__.py
+++ b/src/octoprint/plugins/announcements/__init__.py
@@ -29,8 +29,12 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
octoprint.plugin.TemplatePlugin):
def __init__(self):
- self._cached_channels = dict()
- self._cached_channels_mutex = threading.RLock()
+ self._cached_channel_configs = None
+ self._cached_channel_configs_mutex = threading.RLock()
+
+ from slugify import Slugify
+ self._slugify = Slugify()
+ self._slugify.safe_chars = "-_."
# StartupPlugin
@@ -88,13 +92,18 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
@restricted_access
@admin_permission.require(403)
def get_channel_data(self):
+ from octoprint.settings import valid_boolean_trues
+
result = dict()
- channel_data = self._fetch_all_channels()
+ force = "force" in flask.request.values and flask.request.values["force"] in valid_boolean_trues
+
+ channel_data = self._fetch_all_channels(force=force)
+ channel_configs = self._get_channel_configs(force=force)
- channel_configs = self._get_channel_configs()
enabled = self._settings.get(["enabled_channels"])
forced = self._settings.get(["forced_channels"])
+
for key, data in channel_configs.items():
read_until = channel_configs[key].get("read_until", None)
entries = sorted(self._to_internal_feed(channel_data.get(key, []), read_until=read_until), key=lambda e: e["published"], reverse=True)
@@ -106,7 +115,7 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
result[key] = dict(channel=data["name"],
url=data["url"],
- priority=data["priority"],
+ priority=data.get("priority", 2),
enabled=key in enabled or key in forced,
forced=key in forced,
data=entries,
@@ -140,6 +149,8 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
# Internal Tools
def _mark_read_until(self, channel, until):
+ """Set read_until timestamp of a channel."""
+
current_read_until = None
channel_data = self._settings.get(["channels", channel], merged=True)
if channel_data:
@@ -148,10 +159,14 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
defaults = dict(plugins=dict(announcements=dict(channels=dict())))
defaults["plugins"]["announcements"]["channels"][channel] = dict(read_until=current_read_until)
- self._settings.set(["channels", channel, "read_until"], until, defaults=defaults)
- self._settings.save()
+ with self._cached_channel_configs_mutex:
+ self._settings.set(["channels", channel, "read_until"], until, defaults=defaults)
+ self._settings.save()
+ self._cached_channel_configs = None
def _toggle(self, channel):
+ """Toggle enable/disabled state of a channel."""
+
enabled_channels = list(self._settings.get(["enabled_channels"]))
if channel in enabled_channels:
@@ -162,36 +177,67 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
self._settings.set(["enabled_channels"], enabled_channels)
self._settings.save()
- def _get_channel_configs(self):
- return self._settings.get(["channels"], merged=True)
+ def _get_channel_configs(self, force=False):
+ """Retrieve all channel configs with sanitized keys."""
- def _fetch_all_channels(self):
- with self._cached_channels_mutex:
- channels = self._get_channel_configs()
- enabled = self._settings.get(["enabled_channels"])
- forced = self._settings.get(["forced_channels"])
+ with self._cached_channel_configs_mutex:
+ if self._cached_channel_configs is None or force:
+ configs = self._settings.get(["channels"], merged=True)
+ result = dict()
+ for key, config in configs.items():
+ if "url" not in config or "name" not in config:
+ # strip invalid entries
+ continue
+ result[self._slugify(key)] = config
+ self._cached_channel_configs = result
+ return self._cached_channel_configs
- all_channels = dict()
- for key, config in channels.items():
- if not key in enabled and not key in forced:
- continue
+ def _get_channel_config(self, key, force=False):
+ """Retrieve specific channel config for channel."""
- data = self._get_channel_data(key, config)
- if data is not None:
- all_channels[key] = data
+ safe_key = self._slugify(key)
+ return self._get_channel_configs(force=force).get(safe_key)
- self._cached_channels = all_channels
+ def _fetch_all_channels(self, force=False):
+ """Fetch all channel feeds from cache or network."""
- return self._cached_channels
+ channels = self._get_channel_configs(force=force)
+ enabled = self._settings.get(["enabled_channels"])
+ forced = self._settings.get(["forced_channels"])
+
+ all_channels = dict()
+ for key, config in channels.items():
+ if not key in enabled and not key in forced:
+ continue
+
+ if not "url" in config:
+ continue
+
+ data = self._get_channel_data(key, config, force=force)
+ if data is not None:
+ all_channels[key] = data
+
+ return all_channels
+
+ def _get_channel_data(self, key, config, force=False):
+ """Fetch individual channel feed from cache/network."""
+
+ data = None
+
+ if not force:
+ # we may use the cache, see if we have something in there
+ data = self._get_channel_data_from_cache(key, config)
- def _get_channel_data(self, key, config):
- data = self._get_channel_data_from_cache(key, config)
if data is None:
+ # cache not allowed or empty, fetch from network
data = self._get_channel_data_from_network(key, config)
+
return data
def _get_channel_data_from_cache(self, key, config):
- channel_path = os.path.join(self.get_plugin_data_folder(), "{}.cache".format(key))
+ """Fetch channel feed from cache."""
+
+ channel_path = self._get_channel_cache_path(key)
if os.path.exists(channel_path):
if "ttl" in config and isinstance(config["ttl"], int):
@@ -203,30 +249,35 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
now = time.time()
if os.stat(channel_path).st_mtime + ttl > now:
d = feedparser.parse(channel_path)
- self._logger.info("Loaded channel {} from cache".format(key))
+ self._logger.debug(u"Loaded channel {} from cache at {}".format(key, channel_path))
return d
return None
def _get_channel_data_from_network(self, key, config):
+ """Fetch channel feed from network."""
+
import requests
url = config["url"]
try:
+ start = time.time()
r = requests.get(url)
- self._logger.info("Loaded channel {} from {}".format(key, config["url"]))
+ self._logger.info(u"Loaded channel {} from {} in {:.2}s".format(key, config["url"], time.time() - start))
except Exception as e:
self._logger.exception(
- "Could not fetch channel {} from {}: {}".format(key, config["url"], str(e)))
+ u"Could not fetch channel {} from {}: {}".format(key, config["url"], str(e)))
return None
response = r.text
- channel_path = os.path.join(self.get_plugin_data_folder(), "{}.cache".format(key))
+ channel_path = self._get_channel_cache_path(key)
with codecs.open(channel_path, mode="w", encoding="utf-8") as f:
f.write(response)
return feedparser.parse(response)
def _to_internal_feed(self, feed, read_until=None):
+ """Convert feed to internal data structure."""
+
result = []
if "entries" in feed:
for entry in feed["entries"]:
@@ -236,6 +287,8 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
return result
def _to_internal_entry(self, entry, read_until=None):
+ """Convert feed entries to internal data structure."""
+
published = calendar.timegm(entry["published_parsed"])
read = True
@@ -250,6 +303,12 @@ class AnnouncementPlugin(octoprint.plugin.AssetPlugin,
link=entry["link"],
read=read)
+ def _get_channel_cache_path(self, key):
+ """Retrieve cache path for channel key."""
+
+ safe_key = self._slugify(key)
+ return os.path.join(self.get_plugin_data_folder(), "{}.cache".format(safe_key))
+
_image_tag_re = re.compile(r'')
def _strip_images(text):
diff --git a/src/octoprint/plugins/announcements/static/js/announcements.js b/src/octoprint/plugins/announcements/static/js/announcements.js
index 812d7005..55c20815 100644
--- a/src/octoprint/plugins/announcements/static/js/announcements.js
+++ b/src/octoprint/plugins/announcements/static/js/announcements.js
@@ -97,6 +97,10 @@ $(function() {
})
};
+ self.refreshAnnouncements = function() {
+ self.retrieveData(true);
+ };
+
self.retrieveData = function(force) {
if (!self.loginState.isAdmin()) return;
diff --git a/src/octoprint/plugins/announcements/templates/announcements_settings.jinja2 b/src/octoprint/plugins/announcements/templates/announcements_settings.jinja2
index d9851abe..3d62eeb3 100644
--- a/src/octoprint/plugins/announcements/templates/announcements_settings.jinja2
+++ b/src/octoprint/plugins/announcements/templates/announcements_settings.jinja2
@@ -31,4 +31,4 @@
-
+
diff --git a/src/octoprint/translations/de/LC_MESSAGES/messages.mo b/src/octoprint/translations/de/LC_MESSAGES/messages.mo
index 01adf3ccbdfb18c9136e077c5cdf7ec0366fa83d..aa52755f0e9f932dfa98ffdbfd1ca9df5b6ec6c5 100644
GIT binary patch
delta 2726
zcmXZd4NO*59LMpafP@J0u7GcdAcBMJNmA2_(198a4F`zkg94`HW2yMo=vG<*)okKe
z&KBmDvUKWrD(yvWv#~@a&6F@J(a@P`W|mtn>-+QE?b$x(f9}2K{LlZKd!OCc1NQQp
z80y3tV*8KUw
zqAK&G>l}xMO1l`ff)$vE>rfLmyKyUOfDTjzx-by0p$BiG?oX|>D;|$ZU^=eEVqA(H
zs4aM;%H9`0g+_pGK<#N6s-(;OGs2Q19UTMz5xa;Ju2*mf{k_}mSV^ed(=Zbs!U0%?
zq-9>jJiLf1YQ|Ap1P|)D98~Uws9G#Q9hW6XIUfCJ)VdcMFoAdz>X__7RrgEuVizXi
zgk$y?%t5W?dDKK}-Tr!1BJa8Jc2vTjqmIid+=e~JI38MQ-Eq6p9jF_3qXuY0ZGQ)9
zSMK64jOwtl2emtSsFl5dD)OtSao)w@_z{L-Cu%`Gs6_5MH1uG^H+H}T)bYqf-7pQc
zMRQOUS%aFO0sG+lI1XD-_jjWb{S*6Q@Cjo!U=%juHq>+3C+#@SBpT`{LQPbL%5*hG
z;%nF+-$A`NEjR`bqTa+Cs7eKWYsU%0iNrB(JOiVMOHd1~M$J=$%;T7iG&Jy5)XF}=
zHFzBRVcsd5KoO?);=o}%{cn6{2mAo_+#Za^HcZ8{7=yh|+cOo1N?;5s;fFDX`Asej
zRiFgJaUQC4K70(oR2&ruCrg}UB}f8fj?DLGp)`6pvW6PNu=wXhD$@iyxD
z((}eVg&(6T64GtomU;L(abq|2*Xd2XU<_lKB5Xp>MPu@@9j9XSFUAz$TznjN;y%2N
zpW^l&`xbaF@fSh78E4}q)K=wO=3J3*6RN-9iaiT0R~)-Ht#l0F!U2rM)2O|=ib;3}
zBXRJreBCe$@2wo;iR&;3x8Zbb!A!h`+S25!_Kb{1jpM;6oZ`@!N23Il>3%H1u;1*5
zqY9N!Jr2cYRHBD)CU)XjOuuG7OkPw%FQdK}TikdT4k2#C`>_W#o)dT7zBbvINXInH
z!Yb4uYC=`wAnGmYK&{|3j=;+pj{SbO|3`_gBV9dM!1YJ59p6IzGnd@pBc=EM42=zR
zv~#lb{?5K>C-$LA*Mz~?jB&WbjSr$$(&omuaUk)3s6+=cI$LgrqMlE3<56y$ff0KD
zJv1h8!(_}r2Q|=6T!&xbRLs9+U&oD@Nqh`7!5!2D|6&9N{bef=hw2}WftZe3P!{&Z
zaTvh-CYOc|PacM1fi7Sv_Qttx>_x4t0(D0W=NsAOHXW
delta 2727
zcmXZdeN5F=9LMof7ezpkhbwv^6+{s=E)q)OG`l)XGs_4;MXrD#G3C0YXt~*MZ%ox|nsLB>OG_-;sreGav!Ui{f6E#2!ssj5l0?%MBo=3eOS8Z3Ej!IxOF2evW
z#un5TWG=ApWuu4K7oed%EkKoYE~*mM;b(iL$2(d?_@G_P5gb5#6}w}^A)9*)`iYaV
zFHS|$G8Om)9zYc}_OLBN5^BwZQMu=$iZL1Q9~bONTz;72p$BzzB;#__G1-brY&Vu;
zE2d%pBlZ|fK&_<+HBp({zX+AcD{kC?N_ZFQxHRKC*p6E0!lUl-*l^UoxCJ%9Uexw~
zg4&f1OvNrYPWr~~&T!NMf~X=tkDAcILAV2Z;TNa{wWAX0K>hBvUepBlP{$+wTl+#5
z>ZVRWRb(z|f;!aJtj0085j9{NYMj#;jn{D#cHuT$bIktEcifJXiW;;Y(yQKJ(z>7s2kads+i|HJ75fsBTjJRXE1@d7`5VB)O<^j`5d#Eh6dh+
zTH&Yo5+25$Sa8B7P>2~1aOKfQf5S;T;BM6KKEgz7!wfu&Ui=TWAnz&rel{xMeDpHE
z3D8g}iZK?;P$gW1kK=MwWm-^sdjN~@0;Xf`Y5V&U3=#)%ChkMss?6>bYI(qOg4Lw-$gMHA68pwB^4nHQ^VkK*HNm{nM`5v#{ryWA~XsZqt>6R>#;X{M(ZAXMC7N+=zb`LJ+9#;f_^wDNh~;{u`3;`-41>bmOC(j|VsKfGeW
z=Cp1j^T&?P+17h~dIYUe;k@-L1L0!_n;
diff --git a/src/octoprint/translations/de/LC_MESSAGES/messages.po b/src/octoprint/translations/de/LC_MESSAGES/messages.po
index 41ef251c..e0194ed0 100644
--- a/src/octoprint/translations/de/LC_MESSAGES/messages.po
+++ b/src/octoprint/translations/de/LC_MESSAGES/messages.po
@@ -5,20 +5,21 @@
#
msgid ""
msgstr ""
-"Project-Id-Version: OctoPrint\n"
+"Project-Id-Version: OctoPrint\n"
"Report-Msgid-Bugs-To: i18n@octoprint.org\n"
-"POT-Creation-Date: 2016-05-04 09:51+0200\n"
-"PO-Revision-Date: 2016-05-04 09:47+0100\n"
+"POT-Creation-Date: 2016-05-04 13:26+0200\n"
+"PO-Revision-Date: 2016-05-04 13:27+0100\n"
"Last-Translator: Gina Häußge \n"
"Language: de\n"
"Language-Team: German (http://www.transifex.com/projects/p/octoprint/language/de/)\n"
-"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 2.2.0\n"
+"X-Generator: Poedit 1.6.8\n"
-#: src/octoprint/plugins/announcements/__init__.py:81
+#: src/octoprint/plugins/announcements/__init__.py:85
#: src/octoprint/plugins/announcements/templates/announcements.jinja2:4
#: src/octoprint/plugins/announcements/templates/announcements_navbar.jinja2:1
msgid "Announcements"
@@ -36,15 +37,15 @@ msgstr "Kanal deaktivieren"
msgid "Enable Channel"
msgstr "Kanal aktivieren"
-#: src/octoprint/plugins/announcements/static/js/announcements.js:245
+#: src/octoprint/plugins/announcements/static/js/announcements.js:249
msgid "Later"
msgstr "Später"
-#: src/octoprint/plugins/announcements/static/js/announcements.js:251
+#: src/octoprint/plugins/announcements/static/js/announcements.js:255
msgid "Mark read"
msgstr "Gelesen"
-#: src/octoprint/plugins/announcements/static/js/announcements.js:257
+#: src/octoprint/plugins/announcements/static/js/announcements.js:261
msgid "Read..."
msgstr "Lesen..."
@@ -88,8 +89,8 @@ msgid "Actions"
msgstr "Aktionen"
#: src/octoprint/plugins/announcements/templates/announcements_settings.jinja2:34
-msgid "Show Announcements..."
-msgstr "Ankündigungen anzeigen..."
+msgid "Refresh Announcements"
+msgstr "Ankündigungen aktualisieren"
#: src/octoprint/plugins/cura/templates/cura_settings.jinja2:1
#: src/octoprint/templates/tabs/control.jinja2:97
@@ -821,9 +822,7 @@ msgid ""
" take care of that. Otherwise please take a look at the\n"
" Documentation.\n"
"
\n"
-msgstr ""
-"
Du nutzt eine unveröffentlichte Version von OctoPrint, trackst aber OctoPrint Releases."
-"
Du willst vermutlich, dass OctoPrint stattdessen die entsprechende Entwicklungsversion trackt. Falls Du dein lokales OctoPrint-Checkoutverzeichnis auf einen anderen Branch gewechselt hast, dann wechsle das Tracking einfach auf \"Commit\". Ansonsten wirf einen Blick in die Dokumentation.
"
+msgstr "
Du nutzt eine unveröffentlichte Version von OctoPrint, trackst aber OctoPrint Releases.
Du willst vermutlich, dass OctoPrint stattdessen die entsprechende Entwicklungsversion trackt. Falls Du dein lokales OctoPrint-Checkoutverzeichnis auf einen anderen Branch gewechselt hast, dann wechsle das Tracking einfach auf \"Commit\". Ansonsten wirf einen Blick in die Dokumentation.
Du nutzt eine unveröffentlichte Version von OctoPrint, trackst aber OctoPrint Releases."
-"
Du willst vermutlich, dass OctoPrint stattdessen die entsprechende Entwicklungsversion trackt. Falls Du dein lokales OctoPrint-Checkoutverzeichnis auf einen anderen Branch gewechselt hast, dann wechsle das Tracking einfach auf \"Commit\". Ansonsten wirf einen Blick in die Dokumentation.
"
+msgstr "
Du nutzt eine unveröffentlichte Version von OctoPrint, trackst aber OctoPrint Releases.
Du willst vermutlich, dass OctoPrint stattdessen die entsprechende Entwicklungsversion trackt. Falls Du dein lokales OctoPrint-Checkoutverzeichnis auf einen anderen Branch gewechselt hast, dann wechsle das Tracking einfach auf \"Commit\". Ansonsten wirf einen Blick in die Dokumentation.