From d469324392b3ffd566c5028e7cf2bc9013661961 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 9 Nov 2014 21:17:36 +0100 Subject: [PATCH 01/11] fix 'undefined name 'buffer'' on Python 3.x --- isso/db/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/isso/db/__init__.py b/isso/db/__init__.py index e1f0485..919137c 100644 --- a/isso/db/__init__.py +++ b/isso/db/__init__.py @@ -9,6 +9,8 @@ from collections import defaultdict logger = logging.getLogger("isso") +from isso.compat import buffer + from isso.db.comments import Comments from isso.db.threads import Threads from isso.db.spam import Guard From f06be982e75f1cfc332db71da14c15df39be5f86 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 9 Nov 2014 21:18:05 +0100 Subject: [PATCH 02/11] add target to lint for Python 2 and 3 --- Makefile | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8510d75..22fa937 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ ISSO_JS_DST := isso/js/embed.min.js isso/js/embed.dev.js \ ISSO_CSS := isso/css/isso.css -ISSO_PY_SRC := $(shell git ls-files | grep .py) +ISSO_PY_SRC := $(shell git ls-files | grep -E "^isso/.+.py$$") DOCS_RST_SRC := $(shell find docs/ -type f -name '*.rst') \ $(wildcard docs/_isso/*) \ @@ -30,6 +30,12 @@ all: man js site init: (cd isso/js; bower install almond requirejs requirejs-text jade) +check: + @echo "Python 2.x" + -@python2 -m pyflakes $(ISSO_PY_SRC) + @echo "Python 3.x" + -@python3 -m pyflakes $(ISSO_PY_SRC) + isso/js/%.min.js: $(ISSO_JS_SRC) $(ISSO_CSS) r.js -o isso/js/build.$*.js out=$@ From f0a0f4022380c122f20ac378af012af3514ce447 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 9 Nov 2014 22:13:26 +0100 Subject: [PATCH 03/11] add fallback localStorage implementation if not functional, #134 --- isso/js/app/isso.js | 3 ++- isso/js/app/utils.js | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index ec97d63..21aec7c 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -7,7 +7,8 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", var Postbox = function(parent) { - var el = $.htmlify(jade.render("postbox", { + var localStorage = utils.localStorageImpl, + el = $.htmlify(jade.render("postbox", { "author": JSON.parse(localStorage.getItem("author")), "email": JSON.parse(localStorage.getItem("email")), "website": JSON.parse(localStorage.getItem("website")) diff --git a/isso/js/app/utils.js b/isso/js/app/utils.js index d7bddf4..f971770 100644 --- a/isso/js/app/utils.js +++ b/isso/js/app/utils.js @@ -68,11 +68,34 @@ define(["app/i18n"], function(i18n) { .replace(/\n/gi, '
'); }; + // Safari private browsing mode supports localStorage, but throws QUOTA_EXCEEDED_ERR + var localStorageImpl; + try { + localStorage.setItem("x", "y"); + localStorage.removeItem("x"); + localStorageImpl = localStorage; + } catch (ex) { + localStorageImpl = (function(storage) { + return { + setItem: function(key, val) { + storage[key] = val; + }, + getItem: function(key) { + return typeof(storage[key]) !== 'undefined' ? storage[key] : null; + }, + removeItem: function(key) { + delete storage[key]; + } + }; + })({}); + } + return { cookie: cookie, pad: pad, ago: ago, text: text, - detext: detext + detext: detext, + localStorageImpl: localStorageImpl }; }); From 0d6d975f980f2a0288f57d46105795e79d4141ba Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 9 Nov 2014 22:57:02 +0100 Subject: [PATCH 04/11] use /opt/isso as virtualenv and add a simple sysvinit script The SysVinit script is not tested, but it looks fine to me. Also added a few lines on how to execute `isso` when using a virtualenv from the outside (both changes suggested on IRC). --- docs/docs/install.rst | 64 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/docs/docs/install.rst b/docs/docs/install.rst index fb5415a..72d3689 100644 --- a/docs/docs/install.rst +++ b/docs/docs/install.rst @@ -49,8 +49,8 @@ but not recommended): .. code-block:: sh - ~> virtualenv /path/to/isso - ~> source /path/to/isso/bin/activate + ~> virtualenv /opt/isso + ~> source /opt/isso/bin/activate After calling `source`, you can now install packages from PyPi locally into this virtual environment. If you don't like Isso anymore, you just `rm -rf` the @@ -58,6 +58,11 @@ folder. Inside this virtual environment, you may also execute the example commands from above to upgrade your Python Package Manager (although it barely makes sense), it is completely independent from your global system. +To use Isso installed in a virtual environment outside of the virtual +environment, you just need to add */opt/isso/bin* to your :envvar:`PATH` or +execute */opt/isso/bin/isso* directly. It will launch Isso from within the +virtual environment. + With a virtualenv active, you may now continue to :ref:`install-from-pypi`! Of course you may not need a virtualenv when you are running dedicated virtual machines or a shared host (e.g. Uberspace.de). @@ -112,7 +117,7 @@ For easier execution, you can symlink the executable to a location in your .. code-block:: sh - ~> ln -s /path/to/isso-venv/bin/isso /usr/local/bin/isso + ~> ln -s /opt/isso/bin/isso /usr/local/bin/isso Upgrade ^^^^^^^ @@ -121,7 +126,7 @@ To upgrade Isso, activate your virtual environment again, and run .. code-block:: sh - ~> source /path/to/isso/bin/activate # optional + ~> source /opt/isso/bin/activate # optional ~> pip install --upgrade isso .. _prebuilt-package: @@ -205,7 +210,54 @@ Init scripts to run Isso as a service (check your distribution's documentation for your init-system; e.g. Debian uses SysVinit, Fedora uses SystemD) if you don't use FastCGi or uWSGI: -- SystemD: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.service -- SysVinit: https://github.com/jgraichen/debian-isso/blob/master/debian/isso.init +- SystemD (Isso + Gunicorn): https://github.com/jgraichen/debian-isso/blob/master/debian/isso.service +- SysVinit (Isso + Gunicorn): https://github.com/jgraichen/debian-isso/blob/master/debian/isso.init - OpenBSD: https://gist.github.com/noqqe/7397719 - Supervisor: https://github.com/posativ/isso/issues/47 + +If you're writing your own init script, you can utilize ``start-stop-daemon`` +to run Isso in the background (Isso runs in the foreground usually). Below you +find a very basic SysVinit script which you can use for inspiration: + +.. code-block:: sh + + #!/bin/sh + ### BEGIN INIT INFO + # Provides: isso + # Required-Start: $local_fs $network + # Default-Start: 2 3 4 5 + # Default-Stop: 0 1 6 + # Description: lightweight Disqus alternative + ### END INIT INFO + + EXEC=/opt/isso/bin/isso + EXEC_OPTS="-c /etc/isso.cfg" + + RUNAS=isso + PIDFILE=/var/run/isso.pid + + start() { + echo 'Starting service…' >&2 + start-stop-daemon --start --user "$RUNAS" --background --make-pidfile --pidfile $PIDFILE \ + --exec $EXEC -- $EXEC_OPTS + } + + stop() { + echo 'Stopping service…' >&2 + start-stop-daemon --stop --user "$RUNAS" --pidfile $PIDFILE --exec $EXEC + } + + case "$1" in + start) + start + ;; + stop) + stop + ;; + retart) + stop + start + ;; + *) + echo "Usage: $0 {start|stop|restart}" + esac From 4b64615f92d0fa57dbe243c42ee8bba55105e0c4 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Wed, 12 Nov 2014 22:34:07 +0100 Subject: [PATCH 05/11] set encoding to UTF-8 for Disqus and WordPress import, also fixes #93 --- isso/migrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isso/migrate.py b/isso/migrate.py index 7afd736..5afd6d0 100644 --- a/isso/migrate.py +++ b/isso/migrate.py @@ -164,7 +164,7 @@ class WordPress(object): self.xmlfile = xmlfile self.count = 0 - with io.open(xmlfile) as fp: + with io.open(xmlfile, encoding="utf-8") as fp: ns = WordPress.detect(fp.read(io.DEFAULT_BUFFER_SIZE)) if ns: @@ -257,7 +257,7 @@ def dispatch(type, db, dump): if type is None: - with io.open(dump) as fp: + with io.open(dump, encoding="utf-8") as fp: peek = fp.read(io.DEFAULT_BUFFER_SIZE) if WordPress.detect(peek): From a1be81f6c894cfd3a60a44861d79e6e414e3773b Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 16 Nov 2014 14:25:48 +0100 Subject: [PATCH 06/11] add UnicodeDecodeError to troubleshooting (via #93) --- docs/docs/troubleshooting.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/docs/troubleshooting.rst b/docs/docs/troubleshooting.rst index 21514da..20ee8dd 100644 --- a/docs/docs/troubleshooting.rst +++ b/docs/docs/troubleshooting.rst @@ -12,6 +12,21 @@ Install Isso in a virtual environment as described in :ref:`install-interludium`. Alternatively, you can use `pip install --user` to install Isso into the user's home. +UnicodeDecodeError: 'ascii' codec can't decode byte 0xff +-------------------------------------------------------- + +Likely an issue with your environment, check you set your preferred file +encoding either in :envvar:`LANG`, :envvar:`LANGUAGE`, :envvar:`LC_ALL` or +:envvar:`LC_CTYPE`: + +.. code-block:: text + + $ env LANG=C.UTF-8 isso [-h] [--version] ... + +If none of the mentioned variables is set, the interaction with Isso will +likely fail (unable to print non-ascii characters to stdout/err, unable to +parse configuration file with non-ascii characters and to forth). + The web console shows 404 Not Found responses --------------------------------------------- From 928198f340a4aa1775e28c5eaa28ceabe82b14c5 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Sun, 16 Nov 2014 14:27:42 +0100 Subject: [PATCH 07/11] update translations --- isso/js/app/i18n/bg.js | 27 +++++++++++++++++++++++++++ isso/js/app/i18n/it.js | 2 ++ 2 files changed, 29 insertions(+) create mode 100644 isso/js/app/i18n/bg.js diff --git a/isso/js/app/i18n/bg.js b/isso/js/app/i18n/bg.js new file mode 100644 index 0000000..45ac24b --- /dev/null +++ b/isso/js/app/i18n/bg.js @@ -0,0 +1,27 @@ +define({ + "postbox-text": "Въведете коментара си тук (поне 3 знака)", + "postbox-author": "Име/псевдоним (незадължително)", + "postbox-email": "Ел. поща (незадължително)", + "postbox-website": "Уебсайт (незадължително)", + "postbox-submit": "Публикуване", + "num-comments": "1 коментар\n{{ n }} коментара", + "no-comments": "Все още няма коментари", + "comment-reply": "Отговор", + "comment-edit": "Редактиране", + "comment-save": "Запис", + "comment-delete": "Изтриване", + "comment-confirm": "Потвърждение", + "comment-close": "Затваряне", + "comment-cancel": "Отказ", + "comment-deleted": "Коментарът е изтрит.", + "comment-queued": "Коментарът чака на опашката за модериране.", + "comment-anonymous": "анонимен", + "comment-hidden": "{{ n }} скрити", + "date-now": "сега", + "date-minute": "преди 1 минута\nпреди {{ n }} минути", + "date-hour": "преди 1 час\nпреди {{ n }} часа", + "date-day": "вчера\nпреди {{ n }} дни", + "date-week": "миналата седмица\nпреди {{ n }} седмици", + "date-month": "миналия месец\nпреди {{ n }} месеца", + "date-year": "миналата година\nпреди {{ n }} години" +}); diff --git a/isso/js/app/i18n/it.js b/isso/js/app/i18n/it.js index 7a8bf51..31eeb2c 100644 --- a/isso/js/app/i18n/it.js +++ b/isso/js/app/i18n/it.js @@ -2,6 +2,7 @@ define({ "postbox-text": "Scrivi un commento qui (minimo 3 caratteri)", "postbox-author": "Nome (opzionale)", "postbox-email": "E-mail (opzionale)", + "postbox-website": "Sito web (opzionale)", "postbox-submit": "Invia", "num-comments": "Un Commento\n{{ n }} Commenti", "no-comments": "Ancora Nessun Commento", @@ -15,6 +16,7 @@ define({ "comment-deleted": "Commento eliminato.", "comment-queued": "Commento in coda per moderazione.", "comment-anonymous": "Anonimo", + "comment-hidden": "{{ n }} Nascosto", "date-now": "poco fa", "date-minute": "un minuto fa\n{{ n }} minuti fa", "date-hour": "un ora fa\n{{ n }} ore fa", From 89d6ea076b8ec9056faf037ea34c8022b6146da5 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Tue, 25 Nov 2014 22:59:05 +0100 Subject: [PATCH 08/11] add --empty-id flag to import weird Disqus exports, fixes #135 --- isso/__init__.py | 4 +++- isso/migrate.py | 44 +++++++++++++++++++++++--------------------- 2 files changed, 26 insertions(+), 22 deletions(-) diff --git a/isso/__init__.py b/isso/__init__.py index 527e480..0df6af2 100644 --- a/isso/__init__.py +++ b/isso/__init__.py @@ -212,6 +212,8 @@ def main(): help="perform a trial run with no changes made") imprt.add_argument("-t", "--type", dest="type", default=None, choices=["disqus", "wordpress"], help="export type") + imprt.add_argument("--empty-id", dest="empty_id", action="store_true", + help="workaround for weird Disqus XML exports, #135") serve = subparser.add_parser("run", help="run server") @@ -228,7 +230,7 @@ def main(): dbpath = conf.get("general", "dbpath") mydb = db.SQLite3(dbpath, conf) - migrate.dispatch(args.type, mydb, args.dump) + migrate.dispatch(args.type, mydb, args.dump, args.empty_id) sys.exit(0) diff --git a/isso/migrate.py b/isso/migrate.py index 5afd6d0..a51118e 100644 --- a/isso/migrate.py +++ b/isso/migrate.py @@ -65,12 +65,13 @@ class Disqus(object): ns = '{http://disqus.com}' internals = '{http://disqus.com/disqus-internals}' - def __init__(self, db, xmlfile): + def __init__(self, db, xmlfile, empty_id=False): self.threads = set([]) self.comments = set([]) self.db = db self.xmlfile = xmlfile + self.empty_id = empty_id def insert(self, thread, posts): @@ -117,7 +118,7 @@ class Disqus(object): progress.update(i, thread.find(Disqus.ns + 'id').text) # skip (possibly?) duplicate, but empty thread elements - if thread.find(Disqus.ns + 'id').text is None: + if thread.find(Disqus.ns + 'id').text is None and not self.empty_id: continue id = thread.attrib.get(Disqus.internals + 'id') @@ -132,7 +133,9 @@ class Disqus(object): len(self.threads), len(self.comments))) orphans = set(map(lambda e: e.attrib.get(Disqus.internals + "id"), tree.findall(Disqus.ns + "post"))) - self.comments - if orphans: + if orphans and not self.threads: + print("Isso couldn't import any thread, try again with --empty-id") + elif orphans: print("Found %i orphans:" % len(orphans)) for post in tree.findall(Disqus.ns + "post"): if post.attrib.get(Disqus.internals + "id") not in orphans: @@ -250,25 +253,24 @@ class WordPress(object): return None -def dispatch(type, db, dump): - if db.execute("SELECT * FROM comments").fetchone(): - if input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"): - raise SystemExit("Abort.") +def dispatch(type, db, dump, empty_id=False): + if db.execute("SELECT * FROM comments").fetchone(): + if input("Isso DB is not empty! Continue? [y/N]: ") not in ("y", "Y"): + raise SystemExit("Abort.") - if type is None: + if type is None: + with io.open(dump, encoding="utf-8") as fp: + peek = fp.read(io.DEFAULT_BUFFER_SIZE) - with io.open(dump, encoding="utf-8") as fp: - peek = fp.read(io.DEFAULT_BUFFER_SIZE) + if WordPress.detect(peek): + type = "wordpress" - if WordPress.detect(peek): - type = "wordpress" + if Disqus.detect(peek): + type = "disqus" - if Disqus.detect(peek): - type = "disqus" - - if type == "wordpress": - WordPress(db, dump).migrate() - elif type == "disqus": - Disqus(db, dump).migrate() - else: - raise SystemExit("Unknown format, abort.") + if type == "wordpress": + WordPress(db, dump).migrate() + elif type == "disqus": + Disqus(db, dump, empty_id).migrate() + else: + raise SystemExit("Unknown format, abort.") From c9be737f5e081a95ae214465a1bb52b1d8e3f24c Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Tue, 25 Nov 2014 23:18:50 +0100 Subject: [PATCH 09/11] document broken stdout notifications, #138 --- docs/docs/configuration/server.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/server.rst b/docs/docs/configuration/server.rst index 6faf61f..e1205a1 100644 --- a/docs/docs/configuration/server.rst +++ b/docs/docs/configuration/server.rst @@ -81,7 +81,8 @@ notify Available backends: stdout - Log to standard output. Default, if none selected. + Log to standard output. Default, if none selected. Note, this + functionality is broken since a few releases. smtp Send notifications via SMTP on new comments with activation (if From 4b97684e22d9a1140d789cf35a2aa31048a11688 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Wed, 26 Nov 2014 00:06:28 +0100 Subject: [PATCH 10/11] fix editing when avatars are disabled --- isso/js/app/isso.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/isso/js/app/isso.js b/isso/js/app/isso.js index 21aec7c..61cef97 100644 --- a/isso/js/app/isso.js +++ b/isso/js/app/isso.js @@ -183,7 +183,7 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", $("a.edit", footer).toggle("click", function(toggler) { var edit = $("a.edit", footer); - var avatar = $(".avatar", el, false)[0]; + var avatar = config["avatar"] ? $(".avatar", el, false)[0] : null; edit.textContent = i18n.translate("comment-save"); edit.insertAfter($.new("a.cancel", i18n.translate("comment-cancel"))).on("click", function() { @@ -211,7 +211,7 @@ define(["app/dom", "app/utils", "app/config", "app/api", "app/jade", "app/i18n", }, function(toggler) { var textarea = $(".textarea", text); - var avatar = $(".avatar", el, false)[0]; + var avatar = config["avatar"] ? $(".avatar", el, false)[0] : null; if (! toggler.canceled && textarea !== null) { if (utils.text(textarea.innerHTML).length < 3) { From 83d3c7443c5f974e5c0da774aca870f3b6ea67d6 Mon Sep 17 00:00:00 2001 From: Martin Zimmermann Date: Thu, 25 Dec 2014 21:30:54 +0100 Subject: [PATCH 11/11] remove email field from JSON example --- docs/docs/extras/api.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/docs/extras/api.rst b/docs/docs/extras/api.rst index 366eca1..4c9f1d7 100644 --- a/docs/docs/extras/api.rst +++ b/docs/docs/extras/api.rst @@ -23,7 +23,6 @@ Isso: "mode": 1, "hash": "4505c1eeda98", "author": null, - "email": null, "website": null "created": 1387321261.572392, "modified": null,