from wsgicollection import Collection from config import config, log from model import model from robaccia import render, etag_from_raw_etag, http404, http304, http405, http415 import mcoll import os import cgi import re import sha import view.main from urlparse import urljoin, urlunparse import time from tempfile import mkstemp from mimetypes import guess_extension from subprocess import Popen, PIPE import shutil tz = "%+03d:00" % (-time.altzone/3600,) class AdminCollection(Collection): def __call__(self, environ, start_response): ctype = environ["wsgiorg.routing_args"][1]['ctype'] self.c = model.collection(ctype) self.ctype = ctype return super(type(self), self).__call__(environ, start_response) def list(self, environ, start_response): environ['1812.collection'] = self.ctype return view.main.list(environ, start_response) def retrieve(self, environ, start_response): environ['1812.collection'] = self.ctype return view.main.retrieve(environ, start_response) def main(environ, start_response): return render(environ, start_response, "admin_main.xhtml", {}) class Comments(Collection): def __init__(self, readonly=True): self.readonly = readonly def list(self, environ, start_response): ids = model.recent_comments('entry')[:config.getint("display", "entries_per_page")] comments = [] for (id, comment_id) in ids: member = model.comments('entry', id).get(comment_id) if member: member['entry_id'] = id member['entry'] = model.entry.get(id) comments.append(member) return render(environ, start_response, "comment_list.xhtml", {'comments': comments, 'readonly': self.readonly}) def delete(self, environ, start_response): if self.readonly: return http405(environ, start_response) id = environ["wsgiorg.routing_args"][1]['id'] (id, comment_id) = id.split("-") model.comments('entry', id).delete(comment_id) start_response("303 See Other", [('Location', urljoin(environ['REQUEST_URI'], "."))]) return [] # Atom Publishing Protocol def entry_from_feed(feed): entry = feed.entries[0] member = {} member['title'] = entry.title log.info(entry.title_detail) if 'summary_detail' in entry: member['summary'] = entry.summary_detail.value else: member['summary'] = "" member['content'] = entry.content[0].value return member class AppCollection(Collection): def __call__(self, environ, start_response): ctype = environ["wsgiorg.routing_args"][1].get('ctype', '') if ctype: self.c = model.collection(ctype) self.ctype = ctype self.ismedia = model.ismedia(ctype) return super(type(self), self).__call__(environ, start_response) def _mediafilename(self, id, slug, ext): return os.path.join(config.get(self.ctype, 'media_dir'), id + "-" + slug + ext) def _mediauri(self, id, slug, ext): return urljoin(config.get(self.ctype, 'media_uri'), id + "-" + slug + ext) def get_service_document(self, environ, start_response): entry, media = model.allcollections() return render(environ, start_response, "appservice.svc", {"entry": entry, "media": media }) # GET /drafts/ def list(self, environ, start_response): # page # pulled out from query parms # Add middleware to stuff a cgi.FieldStorage somewhere f = cgi.FieldStorage() page = 0 if f.has_key('page'): page = int(f["page"].value) ids = self.c.id_list_by_updated()[(page*20):((page+1)*20)] raw_etag = '"%s"' % sha.sha("".join(ids)).hexdigest() self_uri = "http://" + environ['SERVER_NAME'] + environ['REQUEST_URI'] next = 0 if self.c.id_list_by_updated()[(page+1*20):((page+2)*20)]: next = page + 1 if page and not ids: return http404(environ, start_response) entry_list = [self.c.get_by_updated_id(id) for id in ids] if entry_list: feedupdated = entry_list[0].updated else: feedupdated = "2006-01-01T12:00:00" return render(environ, start_response, "appfeed.atom", {"entry_list": entry_list, "ctype": self.ctype, "next": next, "self_uri": self_uri, "tz": tz, "feedupdated": feedupdated}, raw_etag = raw_etag) def _putmedia(self, environ): body = environ['wsgi.input'].read() (fd, filename) = mkstemp() file = os.fdopen(fd, 'w+b', -1) # write out the file into tmp file.write(body) file.close() if 'CONTENT_TYPE' not in environ: try: contenttype = Popen(["file", "-i", filename], stdout=PIPE).communicate()[0].split(":")[1].strip() except OSError: pass else: contenttype = environ['CONTENT_TYPE'] # Require the media-type ext = guess_extension(contenttype) summary = '' if contenttype.startswith('image'): imageinfo = Popen(["identify", "-verbose", filename], stdout=PIPE).communicate()[0] match = re.search("Comment:(.*)$", imageinfo, re.MULTILINE) if match: summary = match.groups()[0] return ext, contenttype, summary, filename # POST /drafts/ def create(self, environ, start_response): if self.ismedia: # Read and store the media. # Extract info from the image using 'identify' if appropriate # create the message, including the path to the newly created file ext, contenttype, summary, filename = self._putmedia(environ) if not contenttype: os.unlink(filename) return http415(environ, start_response, "

Sorry, I wasn't passed the content type, and I can't seem to guess it.

") member = {} member['summary'] = summary member['title'] = member['slug'] = environ.get('HTTP_SLUG', '') id = self.c.create(member) entry = self.c.get(id) # Move the temp file into it's target location based on the message id # * write out the file as id - slug . 'ext' destfile = self._mediafilename(id, entry.slug, ext) entry['ext'] = ext entry['contenttype'] = contenttype entry['mediauri'] = self._mediauri(id, entry.slug, entry.ext) self.c.put(id, entry) shutil.move(filename, destfile) os.chmod(destfile, 0644) # * Remember to add a rel="edit-media" link appended with ";media" # -- can be constructed on the fly # maybe add support for multipart-mime here? # and thumbnails? which are included in the content? else: import feedparser feedparser._HTMLSanitizer.acceptable_elements.append("embed") feed = feedparser.parse(environ['wsgi.input']) entry = feed.entries[0] member = entry_from_feed(feed) if 'HTTP_SLUG' in environ: member['slug'] = environ['HTTP_SLUG'] id = self.c.create(member) entry = self.c.get(id) raw_etag = entry['updated'] location = urlunparse((environ['wsgi.url_scheme'], environ['HTTP_HOST'], urljoin(environ['REQUEST_URI'], id + "/"), None, None, None)) return render(environ, start_response, "appentry.atom", {"entry": entry, "ctype": self.ctype, "tz": tz, "ismedia": self.ismedia }, headers = {"location": location, "content-location": location}, status="201 Created", raw_etag=raw_etag) # GET /drafts/1 def retrieve(self, environ, start_response): id = environ["wsgiorg.routing_args"][1]['id'] if id not in self.c: return http404(environ, start_response) entry = self.c.get(id) raw_etag = entry['updated'] return render(environ, start_response, "appentry.atom", {"entry": entry, "ctype": self.ctype, "tz": tz, "ismedia": self.ismedia}, raw_etag = raw_etag) # PUT /drafts/1 def update(self, environ, start_response): import feedparser feedparser._HTMLSanitizer.acceptable_elements.append("embed") if self.ctype == "trash": return http405(environ, start_response) id = environ["wsgiorg.routing_args"][1]['id'] feed = feedparser.parse(environ['wsgi.input']) old_entry = self.c.get(id) raw_etag = old_entry['updated'] etag = etag_from_raw_etag(raw_etag, "appentry.atom") if_match = environ.get('HTTP_IF_MATCH', '') if not if_match: # 405 Only Conditional PUT is allowed start_response("405 Method not allowed", [('Content-Type', 'text/plain')]) return ["Only conditional PUTs, ones with If-Match: headers, are allowed."] elif if_match != etag and if_match != '"*"': # 412 Precondition Failed start_response("412 Precondition Failed", [('Content-Type', 'text/plain')]) return ["A conflict was detected, you seem to have missed an update to this resource while editing."] entry = feed.entries[0] member = entry_from_feed(feed) self.c.put(id, member) start_response("200 Ok", []) return [] # DELETE /drafts/1 def delete(self, environ, start_response): if self.ctype == "trash": return http405(environ, start_response) id = environ["wsgiorg.routing_args"][1]['id'] entry = self.c.get(id) self.c.delete(id) if self.ismedia: os.unlink(self._mediafilename(id, entry.slug, entry.ext)) start_response("200 Ok", []) return [] def get_media(self, environ, start_response): if not self.ismedia: return http404(environ, start_response) id = environ["wsgiorg.routing_args"][1]['id'] entry = self.c.get(id) body = file(self._mediafilename(id, entry.slug, entry.ext), "rb").read() etag = '"%s"' % sha.sha(body).hexdigest() if etag == environ.get('HTTP_IF_NONE_MATCH', ''): return http304(environ, start_response) start_response("200 Ok", [ ('content-type', entry.contenttype), ('etag', etag) ]) return [body] def put_media(self, environ, start_response): if not self.ismedia: return http404(environ, start_response) id = environ["wsgiorg.routing_args"][1]['id'] entry = self.c.get(id) ext, contenttype, summary, filename = self._putmedia(environ) if not contenttype: os.unlink(filename) return http415(environ, start_response, "

Sorry, I wasn't passed the content type, and I can't seem to guess it.

") # Move the temp file into it's target location based on the message id # * write out the file as id - slug . 'ext' destfile = self._mediafilename(id, entry.slug, ext) entry['ext'] = ext entry['contenttype'] = contenttype entry['mediauri'] = self._mediauri(id, entry.slug, entry.ext) self.c.put(id, entry) shutil.move(filename, destfile) os.chmod(destfile, 0644) start_response("200 Ok", []) return [] def delete_media(self, environ, start_response): # Delete's have to be sent to the entry return http405(environ, start_response)