nmhive.git
9 years agoREADME.rst: Unwind 'If ..., returns a 404' -> 'Returns a 404 if ...' master
W. Trevor King [Mon, 22 Sep 2014 17:59:59 +0000 (10:59 -0700)]
README.rst: Unwind 'If ..., returns a 404' -> 'Returns a 404 if ...'

It reads more cleanly if we echo the phrasing of the lead-off
sentences.

9 years agoREADME.rst: Replace tab indentation with spaces
W. Trevor King [Mon, 22 Sep 2014 17:56:46 +0000 (10:56 -0700)]
README.rst: Replace tab indentation with spaces

These snuck in with 7d2299db (README.rst: Document the nmhive.py
endpoints, 2014-09-22) and were causing the block to be rendered as
part of the preformated block starting with the curl command.

9 years agoREADME.rst: Fix 'at' -> 'as' typo in GET /gmane/... example
W. Trevor King [Mon, 22 Sep 2014 17:52:25 +0000 (10:52 -0700)]
README.rst: Fix 'at' -> 'as' typo in GET /gmane/... example

9 years agoREADME.rst: Fix 'to get the [mid] is' wording
W. Trevor King [Mon, 22 Sep 2014 17:49:50 +0000 (10:49 -0700)]
README.rst: Fix 'to get the [mid] is' wording

I think the original was the result of changing my mind partway
through the sentence and not proofreading ;).

9 years agoAdd copyright blurbs to sources v0.1.0
W. Trevor King [Mon, 22 Sep 2014 16:36:03 +0000 (09:36 -0700)]
Add copyright blurbs to sources

We've had a COPYING file since 78335c57 (COPYING: Add the 2-clause BSD
license, 2014-09-21), but these blurbs ensure the license stays
attached to the content even if it ends up being separated from this
repository.  If the injected bookmarklet preserved newlines, that
would make reading the license easier, but at least it's there ;).

9 years agoindex.html: Add usage instructions
W. Trevor King [Mon, 22 Sep 2014 16:26:08 +0000 (09:26 -0700)]
index.html: Add usage instructions

And show the screenshot added by the previous commit.

9 years agostatic/article.png: Add a screen-shot with an article.gmane.org/... page
W. Trevor King [Mon, 22 Sep 2014 16:21:03 +0000 (09:21 -0700)]
static/article.png: Add a screen-shot with an article.gmane.org/... page

Adjust the .gitignore to just ignore the bower-installed
dialog-polyfill, since we do want to version this screenshot.

9 years agonmbug.js: Add 10em left/right margins to the dialog
W. Trevor King [Mon, 22 Sep 2014 15:24:19 +0000 (08:24 -0700)]
nmbug.js: Add 10em left/right margins to the dialog

This avoids overlapping with Gmane's left-side navigation elements
(Home, Reading, Searching, ...).

9 years agonmbug.js: List tags as <a> in a <p> instead of as <li> in a <ul>
W. Trevor King [Mon, 22 Sep 2014 15:19:31 +0000 (08:19 -0700)]
nmbug.js: List tags as <a> in a <p> instead of as <li> in a <ul>

This is more compact, since there are currently 27 tags in notmuch's
nmbug list ;).  I also switched to more generic variable names
(tag_list, tag, and element) so that future tag-name changes are less
invasive.

9 years agoREADME.rst: Document the nmhive.py endpoints
W. Trevor King [Mon, 22 Sep 2014 14:03:07 +0000 (07:03 -0700)]
README.rst: Document the nmhive.py endpoints

9 years agonmhive.py: Add --debug option
W. Trevor King [Mon, 22 Sep 2014 13:57:10 +0000 (06:57 -0700)]
nmhive.py: Add --debug option

I'd removed a hardcoded 'app.debug = True' in bdbfab1f (nmhive.py: Use
notmuch behind GET /tags, 2014-09-21) because it conflicted with a
global, read/write lock.  However, in a98096f1 (nmhive.py: Use
per-request database connections, 2014-09-21) I dropped the global
lock, so enabling debuging is feasible again.

9 years agonmhive.py: Return 400 errors for data-less POSTs
W. Trevor King [Mon, 22 Sep 2014 13:53:48 +0000 (06:53 -0700)]
nmhive.py: Return 400 errors for data-less POSTs

Avoid:

  Traceback (most recent call last):
    File "/.../flask/app.py", line 1836, in __call__
      return self.wsgi_app(environ, start_response)
    File "/.../flask/app.py", line 1820, in wsgi_app
      response = self.make_response(self.handle_exception(e))
    File "/.../flask/app.py", line 1403, in handle_exception
      reraise(exc_type, exc_value, tb)
    File "/.../flask/_compat.py", line 33, in reraise
      raise value
    File "/.../flask/app.py", line 1817, in wsgi_app
      response = self.full_dispatch_request()
    File "/.../flask/app.py", line 1477, in full_dispatch_request
      rv = self.handle_user_exception(e)
    File "/.../flask/app.py", line 1381, in handle_user_exception
      reraise(exc_type, exc_value, tb)
    File "/.../flask/_compat.py", line 33, in reraise
      raise value
    File "/.../flask/app.py", line 1475, in full_dispatch_request
      rv = self.dispatch_request()
    File "/.../flask/app.py", line 1461, in dispatch_request
      return self.view_functions[rule.endpoint](**req.view_args)
    File "/.../nmhive.py", line 59, in message_id_tags
      for change in changes:
  TypeError: 'NoneType' object is not iterable

9 years agoREADME.rst: Mention NMBGIT and NMBPREFIX
W. Trevor King [Mon, 22 Sep 2014 13:27:39 +0000 (06:27 -0700)]
README.rst: Mention NMBGIT and NMBPREFIX

I'm also using my ported-to-Python version of nmbug [1], but that will
hopefully land soon, so I'm not documenting it yet.

[1]: http://thread.gmane.org/gmane.mail.notmuch.general/19007
     id:e630b6763e9d0771718afee41ea15b29bb4a1de8.1409935538.git.wking@tremily.us

9 years agoREADME.rst: Add server-setup instructions
W. Trevor King [Mon, 22 Sep 2014 13:12:33 +0000 (06:12 -0700)]
README.rst: Add server-setup instructions

It's no fun when projects don't tell you how to use them ;).

9 years agonmhive.py: Add --host and --port options
W. Trevor King [Mon, 22 Sep 2014 12:58:57 +0000 (05:58 -0700)]
nmhive.py: Add --host and --port options

Time to polish this up a bit now that it works in testing ;).

9 years agonmbug.js: Insert the dialog at the beginning of body
W. Trevor King [Mon, 22 Sep 2014 02:52:13 +0000 (19:52 -0700)]
nmbug.js: Insert the dialog at the beginning of body

Instead of at the end.  I haven't tested this carefully, but in my
vague memories it seemed to do a better job of positioning the dialog
near the top of the frame.

9 years agonmbug.js: Third attempt at robustly loading the dialog polyfill
W. Trevor King [Mon, 22 Sep 2014 02:46:27 +0000 (19:46 -0700)]
nmbug.js: Third attempt at robustly loading the dialog polyfill

It seems like onload is firing after the browser finishes *fetching*
the target, but before it finishes *parsing* the target.  That's
giving me ReferenceErrors for dialogPolyfill.  This new approach
attacks that directly, and just spins until dialogPolyfill has been
setup (with a bonus wait before using it, just for good measure).  Not
very pretty, but more robust ;).

I also simplified the check that avoids reloading the polyfills.
Instead of looking at the document's head, I just check for
dialogPolyfill.  If we've got native dialogs or dialogPolyfill, don't
bother (re)loading the polyfill scripts.

9 years agonmbug.js: Add a 'frame' argument to nmbug.show
W. Trevor King [Sun, 21 Sep 2014 22:39:34 +0000 (15:39 -0700)]
nmbug.js: Add a 'frame' argument to nmbug.show

It defaults to the root window, but handlers can override it if it
makes sense to launch the dialog in a particular frame.  JavaScript
objects are also scoped within a given window, so we need to attach
the dialog polyfill scripts to the window that will be getting the
dialog, and use frame.dialogPolyfill to register it.

9 years agonmbug.js: Cleanup dialog-polyfill injection to avoid duplicates
W. Trevor King [Sun, 21 Sep 2014 22:14:03 +0000 (15:14 -0700)]
nmbug.js: Cleanup dialog-polyfill injection to avoid duplicates

The bookmarklet used to load these each time it was launched, which
caused duplicate entries if you opened and closed the bookmarklet
multiple times on the same page.

9 years agonmhive.py: Commit tag changes to nmbug
W. Trevor King [Sun, 21 Sep 2014 21:36:07 +0000 (14:36 -0700)]
nmhive.py: Commit tag changes to nmbug

There's a bit of a race here: we need to close the database before
committing so that the change has been flushed to disk for nmbug to
pick up, but that means we're not committing while protected by the
read/write lock.  Ideally, we could flush the database to disk, run
the nmbug commit, and then close the database to release the lock.
Unfortunately there is no "commit changes to the datatabase without
closing" command exposed by notmuch, but maybe I can talk folks into
adding one ;).  On the other hand, the race is against parallel
request that aquires the lock after we release it *and* gets their
commit in before ours, which seems unlikely.  Still, it would be
better if we had a known-safe option here.

9 years agonmhive.py: Use notmuch behind GET/POST /mid/<message_id>
W. Trevor King [Sun, 21 Sep 2014 21:00:52 +0000 (14:00 -0700)]
nmhive.py: Use notmuch behind GET/POST /mid/<message_id>

With more per-request connections.  I don't currently check for
read/write lock contention, but ideally we'd return '202 Accepted' in
those cases and queue the changes for the next successful read/write
connection.

9 years agonmhive.py: Use per-request database connections.
W. Trevor King [Sun, 21 Sep 2014 20:52:27 +0000 (13:52 -0700)]
nmhive.py: Use per-request database connections.

On #notmuch, Austin Clements just said:

  Opening the database always gives you a consistent snapshot, so you
  won't see changes unless you close and reopen it.  However, opening
  in read-only mode is quite cheap.

so I'm going to drop my global connection in favor of per-request
connections.  This also allows me to cleanup after aborted atomic
transactions (when I implement the POST backend), because notmuch does
not currently expose Xapian::WritableDatabase::cancel_transaction.  It
also allows other readers to see the new data, since notmuch only
commits changes when the database connection is closed.  Finally,
David Bremner pointed out that holding the read/write lock for an
extended period of time is just bad form.

9 years agonmhive.py: Use notmuch behind GET /tags
W. Trevor King [Sun, 21 Sep 2014 18:38:33 +0000 (11:38 -0700)]
nmhive.py: Use notmuch behind GET /tags

I stick with the NMBPREFIX environment variable for consistency with
nmbug itself.  I also drop 'app.debug = True', to avoid trouble like:

  $ ./nmhive.py
   * Running on http://0.0.0.0:5000/
   * Restarting with reloader
  A Xapian exception occurred opening database: Unable to get write
    lock on /.../xapian: already locked
  Traceback (most recent call last):
    File "./nmhive.py", line 83, in <module>
      mode=notmuch.Database.MODE.READ_WRITE)
    File "/.../notmuch/database.py", line 154, in __init__
      self.open(path, mode)
    File "/.../notmuch/database.py", line 214, in open
      raise NotmuchError(status)
  notmuch.errors.XapianError

because with 'debug = True', Flask tries to run two instances of this
process simultaneously, but only one can hold the write lock at a
time.  If we want to scale this up to multiple writing
threads/processes, we'll probably want to make the persistant Database
instance read-only, and either acquire a write lock as necessary, or
just instantiate a read/write database for each PUT.  For now, it's
easy enough to just have a single thread.

9 years agoCOPYING: Add the 2-clause BSD license
W. Trevor King [Sun, 21 Sep 2014 17:15:03 +0000 (10:15 -0700)]
COPYING: Add the 2-clause BSD license

From http://opensource.org/licenses/BSD-2-Clause

9 years agobower.json: Mention bookmarklet and server in the description
W. Trevor King [Sun, 21 Sep 2014 17:04:56 +0000 (10:04 -0700)]
bower.json: Mention bookmarklet and server in the description

Nmbug is already distributed.  This project is just about providing an
interface for collaborators that don't have notmuch/nmbug locally.

9 years agonmbug.js: Fill in the POST /mid/<message_id> handling
W. Trevor King [Sun, 21 Sep 2014 13:41:39 +0000 (06:41 -0700)]
nmbug.js: Fill in the POST /mid/<message_id> handling

This is currently only (un)setting one tag per request.  If we want, a
later optimization could queue changes and push them all at once (e.g.
after a user pushes a submit button).  It would also be nice to update
the UI based on the list of returned tags, so we hear about changes
made by parallel parties.

9 years agonmhive.py: Accept Content-Type headers via CORS
W. Trevor King [Sun, 21 Sep 2014 13:30:45 +0000 (06:30 -0700)]
nmhive.py: Accept Content-Type headers via CORS

Using the suggested syntax [1] so I can POST from the bookmarklet.

[1]: https://pypi.python.org/pypi/Flask-Cors/1.9.0/#using-json-with-cors

9 years agonmbug.js: Add tag-toggling with a stub _toggle_tag
W. Trevor King [Sun, 21 Sep 2014 13:06:18 +0000 (06:06 -0700)]
nmbug.js: Add tag-toggling with a stub _toggle_tag

This takes care of binding the new method and adjusting the CSS.

9 years agonmbug.js: Add available-tags fetcher and highlight selected tags
W. Trevor King [Sun, 21 Sep 2014 12:48:25 +0000 (05:48 -0700)]
nmbug.js: Add available-tags fetcher and highlight selected tags

Instead of just listing selected tags, list all available tags (using
nmhive.py's new GET /tags).  Mark the selected tags with a background
color.

9 years agonmbug.js: Use bind to avoid forwarding message_id through _get_tags
W. Trevor King [Sun, 21 Sep 2014 12:35:05 +0000 (05:35 -0700)]
nmbug.js: Use bind to avoid forwarding message_id through _get_tags

Instead of telling _get_tags that the callback will always want the
message_id (in addition to the list of that message's tags), use bind
[1] to add that information when we set the _edit_tags callback.

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

9 years agonmhive.py: Add GET /tags
W. Trevor King [Sun, 21 Sep 2014 12:22:56 +0000 (05:22 -0700)]
nmhive.py: Add GET /tags

We don't want bookmarklet users adding random tags.  Instead, restrict
them to tags that are already in the database, which allows us to keep
a consistent tag-set and present a simpler UI.  The hard-coded
_AVAILABLE_TAGS will be replaced by a notmuch query once I finish with
the UI.

9 years agonmbug.js: Style the dialog with some round corners and a shadow
W. Trevor King [Sun, 21 Sep 2014 12:09:55 +0000 (05:09 -0700)]
nmbug.js: Style the dialog with some round corners and a shadow

The CSS is from Eiji Kitamura's demo [1].  I'm inlining the CSS here
to avoid more dynamic-loading shenanigans like we needed for the
dialog polyfill.  I don't expect this CSS will grow much larger
anyway.

[1]: http://demo.agektmr.com/dialog/#styling

9 years agonmbug.js: Add dialog-polyfill dependency for Firefox
W. Trevor King [Sat, 20 Sep 2014 23:52:38 +0000 (16:52 -0700)]
nmbug.js: Add dialog-polyfill dependency for Firefox

It looks like Firefox doesn't support the <dialog> element yet [1,2],
so this commit dynamically loads the polyfill replacement [3].  Since
the polyfill files load asynchronously, I need to use onload to decide
when they've all made it down (setting the async property to false
didn't seem to work).

[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
[2]: https://bugzilla.mozilla.org/show_bug.cgi?id=840640
[3]: https://github.com/GoogleChrome/dialog-polyfill

9 years agobower.json: Add boilerplate so I can serve static dependencies
W. Trevor King [Sat, 20 Sep 2014 23:07:12 +0000 (16:07 -0700)]
bower.json: Add boilerplate so I can serve static dependencies

It looks like Firefox doesn't support the <dialog> element yet [1,2],
so I'll need the polyfill replacement.  This commit puts the generic
boilerplate in place, so we can revert just the dialog polyfill later,
while leaving this boilerplate in place for other dependencies.

[1]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog
[2]: https://bugzilla.mozilla.org/show_bug.cgi?id=840640

9 years agonmbug.js: Stub out a dialog for editing tags
W. Trevor King [Sat, 20 Sep 2014 22:32:38 +0000 (15:32 -0700)]
nmbug.js: Stub out a dialog for editing tags

It lists the associated Message-ID and tags, but I haven't added
JavaScript to support editing the tag list yet.  I'm using the
<dialog> element [1] to float this dialog over the launching page
(e.g. Gmane), so we don't have to worry about mucking with whatever's
going on there ;).

[1]: https://html.spec.whatwg.org/multipage/forms.html#the-dialog-element

9 years agonmbug.js: Fill in the tag-fetching portion of nmbug.show
W. Trevor King [Sat, 20 Sep 2014 21:17:55 +0000 (14:17 -0700)]
nmbug.js: Fill in the tag-fetching portion of nmbug.show

Ask nmhive for the tags associated with the given Message-ID.

I've also used bind [1] so that the nmbug.show callback get's called
with this pointing to the nmbug object.

[1]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

9 years agonmhive.py: Add GET/POST /mid/<message_id>
W. Trevor King [Sat, 20 Sep 2014 20:17:50 +0000 (13:17 -0700)]
nmhive.py: Add GET/POST /mid/<message_id>

So the bookmarklet can get and set tags on a given message.  This
currently just uses an in-memory store, but eventually we'll drop
notmuch in on the backend.

9 years agoindex.html: Avoid interpreting nmbug.js as text/xml
W. Trevor King [Sat, 20 Sep 2014 19:35:23 +0000 (12:35 -0700)]
index.html: Avoid interpreting nmbug.js as text/xml

When loaded from file:///.../index.html, Firefox seems to assume
nmbug.js is text/xml.  If you're serving nmbug.js from an actual
server, this shouldn't be a problem, but the explicit override makes
local testing easier.

9 years agoindex.html: Supply nmbug.js as a bookmarklet
W. Trevor King [Sat, 20 Sep 2014 18:46:55 +0000 (11:46 -0700)]
index.html: Supply nmbug.js as a bookmarklet

Distribute the bookmarklet as a link, so folks can just drag it up
onto their bookmark toolbar.

We're trying to avoid external dependencies, so I don't want to force
folks to load nmbug.js whenever they run the bookmarklet.  Instead,
load_bookmarklet fetches nmbug.js, adds a bit of wrapping code to
create the bookmarklet, and injects that as the nmbug link's href.

9 years agonmbug.js: Extract the Gmane article id and convert to a Message-ID
W. Trevor King [Sat, 20 Sep 2014 20:00:23 +0000 (13:00 -0700)]
nmbug.js: Extract the Gmane article id and convert to a Message-ID

Using some JavaScript gymnastics to look through the available frames
for an article.gmane.org/ URL.  For example, we might be on a page
like:

  http://thread.gmane.org/gmane.mail.notmuch.general/19055/focus=19056

which is composed of the following frames [1]:

  http://news.gmane.org/group/gmane.mail.notmuch.general/thread=19055/force_load=t/focus=19056
  http://article.gmane.org/gmane.mail.notmuch.general/19056
  http://news.gmane.org/navbar.php?group=gmane.mail.notmuch.general&article=19056&next=19057&prev=19054&newsrc=,19056

Or we might be on the article page directly.  There are also permalink
pages (with 'permalink' instead of 'article' available from the
blog-link view), but I'm not worrying about them yet.

[1]: for (var i = 0; i < window.frames.length; i++) {
       console.log(window.frames[i].document.URL);
     }

9 years agonmhive.py: Add GET /gmane/<group>/<int:article>
W. Trevor King [Sat, 20 Sep 2014 19:51:37 +0000 (12:51 -0700)]
nmhive.py: Add GET /gmane/<group>/<int:article>

Gmane's download endpoint [1] doesn't allow cross-origin requests, and
article.gmane.org -> download.gmane.org is a cross-origin request [2].
Work around that with this proxy endpoint, which uses Flask-Cors [3]
to accept all origins.  The bookmarklet can figure out the current
message's group and article id, and hit this endpoint.  Then nmhive
will use Gmane's download endpoint to fetch the message as an mbox,
after which we can use Python's stdlib to extract the Message-ID from
the mbox, and return the extracted Message-ID to the bookmarklet.

Later on we can also add local caching and rate-limiting here, so we
don't bother Gmane more than necessary (the downloads are somewhat
expensive [1]).

[1]: http://gmane.org/export.php
[2]: https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#Definition_of_an_origin
[3]: https://pypi.python.org/pypi/Flask-Cors/

9 years agonmbug.js: Sketch out the bookmarklet framework
W. Trevor King [Sat, 20 Sep 2014 18:46:37 +0000 (11:46 -0700)]
nmbug.js: Sketch out the bookmarklet framework

We'll have a generic nmbug.show() to handle the UI for tagging a given
message (using its Message-ID).  To get that Message-ID, we'll have a
list of potential handlers.  When the bookmarklet fires (run()), we'll
iterate through the handlers unril handler.regexp matches
document.URL.  For the first match, we'll run handler.handle, which
will do whatever it needs to figure out the Message-ID, and then
launch nmbug.show (passed in via 'callback') with the extracted id.

9 years agoREADME.rst: Start outlining the project
W. Trevor King [Sat, 20 Sep 2014 18:42:49 +0000 (11:42 -0700)]
README.rst: Start outlining the project