Commits

Adam Gomaa  committed d9980ec

convert to a Flask app. Getting the hang of this :)

  • Participants
  • Parent commits 6653c58

Comments (0)

Files changed (4)

File TODO

-TODO
-
-Flesh out this TODO:
-
-  * Play tracking. I'll provide hooks, but probably not have it
-    built-in.
-  * Color themes & the like
-  * The "all artists on one page" concept
-  * Get a better way of setting the config options
-  * Searching albums/songs
-  * Expand the controls section & add more track info
-  * path based app state using pushstate - bookmark an artist/song, etc
-  * If I do playlists etc, same idea.
-  * How to persist artists/albums. Lots of ambiguity, the same data is
-    in every track; one big meh.
-  * I can't search for Tiësto. I don't want to learn how to use
-    compose keys. I want to search by ascii.
-  * Progress on reindexing
-  * When clicking the currently-playing artist, automatically select
-    the current album & song
-  * Same for album - clicking on current album should select currently
-    playing song
-
-
-Artist Layout
-=============
-
-Artists are currently just floated, and then the container div width
-is adjusted so that the artists fit within the window height. While
-this keeps an artist's Y-position pretty consistent, different reflows
-when the page loads and changes in the library can move artists from
-the end of one line to the beginning of the next.
-
-What I need, then, is a space-filling curve. I'm not well-versed in
-them to begin with, and my understanding is that the typical case
-would assume a consistent item size; since the length of artist names
-varies, I don't know how well I can get it to work.
-
-

File webmusic/__init__.py

 import re
-import os.path
+import os
 
+from flask import Flask
 
-default_config = {
+class WebMusic(Flask):
+    def get_index(self):
+        from .index import MongoIndex
+        return MongoIndex(self.config["LIBRARY_ROOT"],
+                          self.config["MONGO_URI"],
+                          self.config["MONGO_COLLECTION"])
+
+    def get_template_source(self, path):
+        source, fname, uptodate = self.jinja_env.loader.get_source(None, path)
+        return source
+
+app = WebMusic(__name__)
+
+class DefaultConfig:
     # if your music library location isn't /home/user/Music, set this
     # accordingly.
-    "library-root": os.path.join(os.path.expanduser("~"), u"Music/"),
-
-    # Your jinja2 environment to override the default
-    "jinja2-env": None,
+    LIBRARY_ROOT = os.path.join(os.path.expanduser("~"), u"Music/")
 
     # Enable if you can set this up behind an X-SendFile-capable
     # server.
-    "sendfile": False,
+    SENDFILE = False
 
     # Collection name
-    "mongo-collection": "music",
+    MONGO_COLLECTION = "music"
 
-    # you *must* set mongo-uri, from your code - I suggest in the
-    # settings file - before anything will work.
-    #
     # Format:
-    #
     # mongodb://[username:password@]host1[:port1],...[,hostN[:portN]][/[database][?options]]
-    #
     # see http://www.mongodb.org/display/DOCS/Connections for details.
-    "mongo-uri": None,
+    MONGO_URI = None
 
-}
 
-def get_config(config):
-    return dict(default_config, **config)
+if "WEBMUSIC_SETTINGS" in os.environ:
+    app.config.from_envvar("WEBMUSIC_SETTINGS")
+else:
+    app.config.from_object("webmusic.DefaultConfig")
 
-def make_view(config):
-    merged = get_config(config)
-    def _webmusic(request, path):
-        import webmusic.views
-        return webmusic.views.dispatch(request, path, merged)
-    _webmusic.config = config
-    # add the view function so it can be reversed
-    merged["_view"] = _webmusic
-    return _webmusic
-
-
-def get_app_directory():
-    "Get the application directory - the directory of this package."
-    return os.path.dirname(__file__)
-
-
-def get_jinja2_environment(config):
-    if config["jinja2-env"] is None:
-        config["jinja2-env"] = _get_jinja2_environment()
-    return config["jinja2-env"]
-
-
-def _get_jinja2_environment():
-    import jinja2
-    import os.path
-    env = jinja2.Environment(
-        loader=jinja2.FileSystemLoader(os.path.join(
-                get_app_directory(), "templates")),
-        autoescape=True,
-        extensions=["jinja2.ext.autoescape", "jinja2.ext.with_",
-                    "jinja2.ext.i18n", "jinja2.ext.do",
-                    "jinja2.ext.loopcontrols",],)
-    return env
-
-
-def get_template(config, template_name):
-    return get_jinja2_environment(config).get_template(template_name)
-
-
-def get_template_source(config, path):
-    env = get_jinja2_environment(config)
-    source, fname, uptodate = env.loader.get_source(None, path)
-    return source
 
 _human_helper_rx = re.compile('([0-9]+)')
 def _human_sort_key(string):
     return [int(c) if c.isdigit() else c for c in _human_helper_rx.split(string)]
+
+from . import views

File webmusic/index.py

         ".mp3": mutagen.mp3.EasyMP3,
         ".mp4": mutagen.easymp4.EasyMP4,
         ".ogg": mutagen.oggvorbis.OggVorbis}
-    def __init__(self, config):
+    def __init__(self, root, mongo_uri, mongo_collection):
         import pymongo
 
-        root = config["library-root"]
-
         # TPE2 is "Band/orchestra/accompaniment", but apparently
         # iTunes uses it for albumartist, and everyone else followed
         # suit. Not sure how I feel about that.
         if not root.endswith("/"): root += "/"
         self.root = root
 
-        self.conn = pymongo.Connection(config["mongo-uri"])
-        _db_name = self._pymongo_uri_get_database_name(config["mongo-uri"])
+        self.conn = pymongo.Connection(mongo_uri)
+        _db_name = self._pymongo_uri_get_database_name(mongo_uri)
         self.db = self.conn[_db_name]
-        self.collection = self.db[config["mongo-collection"]]
+        self.collection = self.db[mongo_collection]
 
     def _pymongo_uri_get_database_name(self, uri):
         """Get the database name from a mongodb uri.

File webmusic/views.py

 import json
 import bson.json_util
 
-from django.http import HttpResponse, Http404
-
-from django.contrib.auth.decorators import login_required
-from django.conf import settings
+from . import app
+from flask import request, render_template, url_for, Response
 
 import webmusic
 import webmusic.index
 
 
-def dispatch(request, path, config):
-    if path == "":
-        return web(request, config)
-    elif path == "_ajax":
-        return ajax(request, config)
-    elif path.startswith("getmedia/"):
-        return get_media(request, path[len("getmedia/"):], config)
-    else:
-        raise Http404(u"Unknown music path:{0}".format(path))
-
 def _json_dumps(obj, *args, **kwargs):
     if kwargs.get('indent') is None:
-        if settings.DEBUG: kwargs['indent'] = 4
-        else:              kwargs['indent'] = 0
+        if app.debug: kwargs['indent'] = 4
+        else:         kwargs['indent'] = 0
     if kwargs.get('default') is None:
         kwargs['default'] = bson.json_util.default
 
         kwargs['objecthook'] = bson.json_util.objecthook
     return json.loads(data, *args, **kwargs)
 
-def _get_index(config):
-    if "idx" not in config:
-        config["idx"] = webmusic.index.MongoIndex(config)
-    return config["idx"]
 
-
-@login_required
-def web(request, config):
-    from django.core.urlresolvers import reverse
-
+@app.route("/")
+def web():
     context = {
         "request": request,
-        # the config['_view'] is the view function object
-        "music_url": reverse(config['_view'], args=[""]),
+        "music_url": url_for("web"),
         }
 
-    return HttpResponse(webmusic.get_template(config, "music.html").render(context))
+    return render_template("music.html", **context)
 
 
 def _get_title(track):
     return _title
 
 
-@login_required
-def ajax(request, config):
+@app.route("/_ajax", methods=["POST"])
+def ajax():
     "Handle AJAX requests - requires an 'action' POST key"
     import datetime
-    action = request.POST["action"]
-    res = HttpResponse(content_type="application/json")
-    idx = _get_index(config)
+    action = request.form["action"]
+    res = Response(mimetype="application/json")
+    idx = app.get_index()
     if action == "get-artists":
         artists = idx.get_artists()
-        res.write(_json_dumps(list(artists)))
+        res.stream.write(_json_dumps(list(artists)))
     elif action == "get-albums":
-        _artist = idx._get_dbref(request.POST["artist"])
-        res.write(_json_dumps(list(idx.get_artist_albums(_artist))))
+        _artist = idx._get_dbref(request.form["artist"])
+        res.stream.write(_json_dumps(list(idx.get_artist_albums(_artist))))
     elif action == "get-songs":
         # need to distinguish these in the UI, haven't decided yet
         # how. So just going with showing from all artists for a while
         # to see how big a problem it is.
         if False:
             tracks = idx.get_artist_album_tracks(
-                request.POST["artist"], request.POST["album"])
+                request.form["artist"], request.form["album"])
         else:
-            tracks = idx.get_album_tracks(idx._get_dbref(request.POST["album"]))
+            tracks = idx.get_album_tracks(idx._get_dbref(request.form["album"]))
         tracks = list(tracks)
         for track in tracks:
             track['title'] = _get_title(track)
 
         tracks.sort(key=lambda x: webmusic._human_sort_key(
                 u"{0}|{1}".format(x["_tags"].get("discnumber", ""), x["title"])))
-        res.write(_json_dumps(tracks))
+        res.stream.write(_json_dumps(tracks))
     elif action == "reindex":
         _start_index(idx)
-        res.write(_json_dumps(True))
+        res.stream.write(_json_dumps(True))
     else:
         raise NotImplementedError("unknown action {0!r}".format(action))
     return res
     thread = threading.Thread(target=idx.index)
     thread.start()
 
-@login_required
-def get_media(request, path, config):
+@app.route("/getmedia/<path>")
+def get_media(path):
     """Entry point for getting media.
 
     track _id is passed in the pathname, look up the path for the
     """
     # magic values for app files (which aren't really media, but whatever)
     if path in ['__music.js', '__music.css', '__body.html', '__artists.html']:
-        return get_app_file(request, path, config)
+        return get_app_file(path)
 
-    return get_media_by_id(path, config)
+    return get_media_by_id(path)
 
-def get_media_by_id(_id, config):
+def get_media_by_id(_id):
     "Return media by the document ID."
     import bson.objectid
-    idx = _get_index(config)
+    idx = app.get_index()
     doc = idx.collection.find_one({"_id": bson.objectid.ObjectId(_id)})
-    return get_media_path(doc['path'], config)
+    return get_media_path(doc['path'])
 
 
-def get_media_path(fpath, config):
+def get_media_path(fpath):
     from os.path import abspath, join
-    fpath = abspath(join(config["library-root"], fpath))
-    assert fpath.startswith(config["library-root"])
-    response = HttpResponse(content_type="application/octet-stream")
-    if config["sendfile"]:
-        response["X-SendFile"] = fpath.encode("utf-8")
-        response["X-fakesf"] = fpath.encode("utf-8")
+    fpath = abspath(join(app.config["LIBRARY_ROOT"], fpath))
+    assert fpath.startswith(app.config["LIBRARY_ROOT"])
+    response = Response(mimetype="application/octet-stream")
+    if app.config["SENDFILE"]:
+        response.headers["X-SendFile"] = fpath.encode("utf-8")
+        # for debugging
+        response.headers["X-fakesf"] = fpath.encode("utf-8")
     else:
-        response.write(open(fpath, 'r').read())
+        response.stream.write(open(fpath, 'r').read())
     return response
 
 
-def get_app_file(request, fpath, config):
+def get_app_file(fpath):
     if fpath == '__music.js':
-        return HttpResponse(
-            webmusic.get_template_source(config, 'music.js'),
-            content_type='application/javascript')
+        return Response(
+            app.get_template_source('music.js'),
+            mimetype='application/javascript')
     elif fpath == '__music.css':
-        return HttpResponse(
-            webmusic.get_template_source(config, 'music.css'),
-            content_type='text/css')
+        return Response(
+            app.get_template_source('music.css'),
+            mimetype='text/css')
     elif fpath == '__body.html':
-        return HttpResponse(
-            webmusic.get_template_source(config, 'music-body.html'),
-            content_type='text/html')
+        return Response(
+            app.get_template_source('music-body.html'),
+            mimetype='text/html')