def rotate_photo(key, rotate): """Rotate a photo 90 degrees to the left or right.""" photo = get_photo(key) if not photo: return # Degrees to rotate. degrees = None if rotate == "left": degrees = 90 elif rotate == "right": degrees = -90 else: degrees = 180 new_names = dict() for size in ["large", "thumb", "avatar"]: fname = os.path.join(Config.photo.root_private, photo[size]) logger.info("Rotating image {} by {} degrees.".format(fname, degrees)) # Give it a new name. filetype = fname.split(".")[-1] outfile = random_name(filetype) new_names[size] = outfile img = Image.open(fname) img = img.rotate(degrees) img.save(os.path.join(Config.photo.root_private, outfile)) # Delete the old name. os.unlink(fname) # Save the new image names. edit_photo(key, new_names)
def send_email(to, subject, message, sender=None, reply_to=None): """Send an e-mail out.""" if sender is None: sender = Config.mail.sender if type(to) != list: to = [to] logger.info("Send email to {}".format(to)) if Config.mail.method == "smtp": # Send mail with SMTP. for email in to: msg = MIMEMultipart("alternative") msg.set_charset("utf-8") msg["Subject"] = subject msg["From"] = sender msg["To"] = email if reply_to is not None: msg["Reply-To"] = reply_to text = MIMEText(message, "plain", "utf-8") msg.attach(text) # Send the e-mail. try: server = smtplib.SMTP(Config.mail.server, Config.mail.port) server.sendmail(sender, [email], msg.as_string()) except: pass
def delete(document): """Delete a document from the DB.""" path = mkpath(document) if os.path.isfile(path): logger.info("Delete DB document: {}".format(path)) os.unlink(path) del_cache(document)
def edit_page(name, author, body, note, history=True): """Write to a page.""" name = name.strip("/") # Remove any surrounding slashes. # Get the old page first. page = get_page(name) if not page: # Initialize the page. page = dict( revisions=[], ) # The new revision to be added. rev = dict( id=hashlib.md5(str(int(time.time())).encode("utf-8")).hexdigest(), time=int(time.time()), author=author, body=body, note=note or "Updated the page.", ) # Updating the history? if history: page["revisions"].insert(0, rev) else: # Replacing the original item. if len(page["revisions"]): page["revisions"][0] = rev else: page["revisions"].append(rev) # Write it. logger.info("Write to Wiki page {}".format(name)) JsonDB.commit("wiki/pages/{}".format(name), page) return True
def edit_page(name, author, body, note, history=True): """Write to a page.""" name = name.strip("/") # Remove any surrounding slashes. # Get the old page first. page = get_page(name) if not page: # Initialize the page. page = dict(revisions=[], ) # The new revision to be added. rev = dict( id=hashlib.md5(str(int(time.time())).encode("utf-8")).hexdigest(), time=int(time.time()), author=author, body=body, note=note or "Updated the page.", ) # Updating the history? if history: page["revisions"].insert(0, rev) else: # Replacing the original item. if len(page["revisions"]): page["revisions"][0] = rev else: page["revisions"].append(rev) # Write it. logger.info("Write to Wiki page {}".format(name)) JsonDB.commit("wiki/pages/{}".format(name), page) return True
def delete_history(name, revision): """Delete a revision entry from the history.""" name = name.strip("/") # Get page first. page = get_page(name) if not page: return None # Revise history. history = list() for rev in page["revisions"]: if rev["id"] == revision: logger.info("Delete history ID {} from Wiki page {}".format(revision, name)) continue history.append(rev) # Empty history = delete the page. if len(history) == 0: logger.info("Deleted last history item; Remove Wiki page {}".format(name)) return delete_page(name) page["revisions"] = history JsonDB.commit("wiki/pages/{}".format(name), page) return True
def delete_history(name, revision): """Delete a revision entry from the history.""" name = name.strip("/") # Get page first. page = get_page(name) if not page: return None # Revise history. history = list() for rev in page["revisions"]: if rev["id"] == revision: logger.info("Delete history ID {} from Wiki page {}".format( revision, name)) continue history.append(rev) # Empty history = delete the page. if len(history) == 0: logger.info( "Deleted last history item; Remove Wiki page {}".format(name)) return delete_page(name) page["revisions"] = history JsonDB.commit("wiki/pages/{}".format(name), page) return True
def set_album_cover(album, key): """Change the album's cover photo.""" album = sanitize_name(album) index = get_index() logger.info("Changing album cover for {} to {}".format(album, key)) if album in index["albums"] and key in index["albums"][album]: index["covers"][album] = key write_index(index) return logger.error("Failed to change album index! Album or photo not found.")
def delete_page(name): """Completely delete a wiki page.""" name = name.strip("/") path = "wiki/pages/{}".format(name) if JsonDB.exists(path): logger.info("Delete Wiki page {}".format(name)) JsonDB.delete(path) return True
def add_subscriber(thread, email): """Add a subscriber to a thread.""" if not "@" in email: return # Sanity check: only subscribe to threads that exist. if not JsonDB.exists("comments/threads/{}".format(thread)): return logger.info("Subscribe e-mail {} to thread {}".format(email, thread)) subs = get_subscribers(thread) subs[email] = int(time.time()) write_subscribers(thread, subs)
def edit_photo(key, data): """Update a photo's data.""" index = get_index() if not key in index["map"]: logger.warning("Tried to delete photo {} but it wasn't found?".format(key)) return album = index["map"][key] logger.info("Updating data for the photo {} from album {}".format(key, album)) index["albums"][album][key].update(data) write_index(index)
def create(username, password, name=None, uid=None, role="user"): """Create a new user account. Returns the user ID number assigned to this user.""" # Name defaults to username. if name is None: name = username username = username.lower() # Provided with a user ID? if uid is not None: # See if it's available. if exists(uid=uid): logger.warning("Wanted to use UID {} for user {} but it wasn't available.".format(uid, username)) uid = None # Need to generate a UID? if uid is None: uid = get_next_uid() uid = int(uid) # Username musn't exist. if exists(username): # The front-end shouldn't let this happen. raise Exception("Can't create username {}: already exists!".format(username)) # Crypt their password. hashedpass = hash_password(password) logger.info("Create user {} with username {}".format(uid, username)) # Create the user file. JsonDB.commit("users/by-id/{}".format(uid), dict( uid=uid, username=username, name=name, picture="", role=role, password=hashedpass, created=time.time(), )) # And their username to ID map. JsonDB.commit("users/by-name/{}".format(username), dict( uid=uid, )) return uid
def edit_photo(key, data): """Update a photo's data.""" index = get_index() if not key in index["map"]: logger.warning( "Tried to delete photo {} but it wasn't found?".format(key)) return album = index["map"][key] logger.info("Updating data for the photo {} from album {}".format( key, album)) index["albums"][album][key].update(data) write_index(index)
def unsubscribe(thread, email): """Unsubscribe an e-mail address from a thread. If `thread` is `*`, the e-mail is unsubscribed from all threads.""" # Which threads to unsubscribe from? threads = [] if thread == "*": threads = JsonDB.list_docs("comments/subscribers") else: threads = [thread] # Remove them as a subscriber. for thread in threads: if JsonDB.exists("comments/subscribers/{}".format(thread)): logger.info("Unsubscribe e-mail address {} from comment thread {}".format(email, thread)) db = get_subscribers(thread) del db[email] write_subscribers(thread, db)
def delete_photo(key): """Delete a photo.""" index = get_index() if not key in index["map"]: logger.warning( "Tried to delete photo {} but it wasn't found?".format(key)) return album = index["map"][key] logger.info("Completely deleting the photo {} from album {}".format( key, album)) photo = index["albums"][album][key] # Delete all the images. for size in ["large", "thumb", "avatar"]: logger.info("Delete: {}".format(photo[size])) fname = os.path.join(Config.photo.root_private, photo[size]) if os.path.isfile(fname): os.unlink(fname) # Delete it from the sort list. index["photo-order"][album].remove(key) del index["map"][key] del index["albums"][album][key] # Was this the album cover? if index["covers"][album] == key: # Try to pick a new one. if len(index["photo-order"][album]) > 0: index["covers"][album] = index["photo-order"][album][0] else: index["covers"][album] = "" # If the album is empty now too, delete it as well. if len(index["albums"][album].keys()) == 0: del index["albums"][album] del index["photo-order"][album] del index["covers"][album] index["album-order"].remove(album) write_index(index)
def unsubscribe(thread, email): """Unsubscribe an e-mail address from a thread. If `thread` is `*`, the e-mail is unsubscribed from all threads.""" # Which threads to unsubscribe from? threads = [] if thread == "*": threads = JsonDB.list_docs("comments/subscribers") else: threads = [thread] # Remove them as a subscriber. for thread in threads: if JsonDB.exists("comments/subscribers/{}".format(thread)): logger.info( "Unsubscribe e-mail address {} from comment thread {}".format( email, thread)) db = get_subscribers(thread) del db[email] write_subscribers(thread, db)
def delete_photo(key): """Delete a photo.""" index = get_index() if not key in index["map"]: logger.warning("Tried to delete photo {} but it wasn't found?".format(key)) return album = index["map"][key] logger.info("Completely deleting the photo {} from album {}".format(key, album)) photo = index["albums"][album][key] # Delete all the images. for size in ["large", "thumb", "avatar"]: logger.info("Delete: {}".format(photo[size])) fname = os.path.join(Config.photo.root_private, photo[size]) if os.path.isfile(fname): os.unlink(fname) # Delete it from the sort list. index["photo-order"][album].remove(key) del index["map"][key] del index["albums"][album][key] # Was this the album cover? if index["covers"][album] == key: # Try to pick a new one. if len(index["photo-order"][album]) > 0: index["covers"][album] = index["photo-order"][album][0] else: index["covers"][album] = "" # If the album is empty now too, delete it as well. if len(index["albums"][album].keys()) == 0: del index["albums"][album] del index["photo-order"][album] del index["covers"][album] index["album-order"].remove(album) write_index(index)
def load_theme(): """Pre-load and cache the emoticon theme. This happens on startup.""" theme = Config.emoticons.theme global _cache # Cached? if _cache: return _cache # Only if the theme file exists. settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json") if not os.path.isfile(settings): logger.error("Failed to load smiley theme {}: not found!") # Try the default (tango). theme = "tango" settings = os.path.join(Config.emoticons.root_private, theme, "emoticons.json") if os.path.isfile(settings): logger.info("Falling back to default theme: tango") else: # Give up. return {} # Read it. fh = codecs.open(settings, "r", "utf-8") text = fh.read() fh.close() try: data = json.loads(text) except Exception as e: logger.error("Couldn't load JSON from emoticon file: {}".format(e)) data = {} # Cache and return it. _cache = data return data
def send_email(to, subject, message, header=None, footer=None, sender=None, reply_to=None): """Send a (markdown-formatted) e-mail out. This will deliver an HTML-formatted e-mail (using the ``email.inc.html`` template) using the rendered Markdown contents of ``message`` and ``footer``. It will also send a plain text version using the raw Markdown formatting in case the user can't accept HTML. Parameters: to ([]str): list of addresses to send the message to. subject (str): email subject and title. message (str): the email body, in Markdown format. header (str): the header text for the HTML email (plain text). footer (str): optional email footer, in Markdown format. The default footer is defined in the ``email.inc.html`` template. sender (str): optional sender email address. Defaults to the one specified in the site configuration. reply_to (str): optional Reply-To address header. """ if sender is None: sender = Config.mail.sender if type(to) != list: to = [to] # Render the Markdown bits. if footer: footer = render_markdown(footer) # Default header matches the subject. if not header: header = subject html_message = render_template("email.inc.html", title=subject, header=header, message=render_markdown(message), footer=footer, ) logger.info("Send email to {}".format(to)) if Config.mail.method == "smtp": # Send mail with SMTP. for email in to: msg = MIMEMultipart("alternative") msg.set_charset("utf-8") msg["Subject"] = subject msg["From"] = sender msg["To"] = email if reply_to is not None: msg["Reply-To"] = reply_to text = MIMEText(message, "plain", "utf-8") msg.attach(text) html = MIMEText(html_message, "html", "utf-8") msg.attach(html) # Send the e-mail. try: server = smtplib.SMTP(Config.mail.server, Config.mail.port) server.sendmail(sender, [email], msg.as_string()) except: pass
def update(): """Post/edit a blog entry.""" # Get our available avatars. g.info["avatars"] = Blog.list_avatars() g.info["userpic"] = User.get_picture(uid=g.info["session"]["uid"]) # Default vars. g.info.update(dict( post_id="", fid="", author=g.info["session"]["uid"], subject="", body="", format="markdown", avatar="", categories="", privacy=Config.blog.default_privacy, emoticons=True, comments=Config.blog.allow_comments, month="", day="", year="", hour="", min="", sec="", preview=False, )) # Editing an existing post? post_id = request.args.get("id", None) if post_id: post_id = Blog.resolve_id(post_id) if post_id: logger.info("Editing existing blog post {}".format(post_id)) post = Blog.get_entry(post_id) g.info["post_id"] = post_id g.info["post"] = post # Copy fields. for field in ["author", "fid", "subject", "format", "format", "body", "avatar", "categories", "privacy", "emoticons", "comments"]: g.info[field] = post[field] # Dissect the time. date = datetime.datetime.fromtimestamp(post["time"]) g.info.update(dict( month="{:02d}".format(date.month), day="{:02d}".format(date.day), year=date.year, hour="{:02d}".format(date.hour), min="{:02d}".format(date.minute), sec="{:02d}".format(date.second), )) # Are we SUBMITTING the form? if request.method == "POST": action = request.form.get("action") # Get all the fields from the posted params. g.info["post_id"] = request.form.get("id") for field in ["fid", "subject", "format", "body", "avatar", "categories", "privacy"]: g.info[field] = request.form.get(field) for boolean in ["emoticons", "comments"]: g.info[boolean] = True if request.form.get(boolean, None) == "true" else False for number in ["author", "month", "day", "year", "hour", "min", "sec"]: g.info[number] = int(request.form.get(number, 0)) # What action are they doing? if action == "preview": g.info["preview"] = True # Render markdown? if g.info["format"] == "markdown": g.info["rendered_body"] = render_markdown(g.info["body"]) else: g.info["rendered_body"] = g.info["body"] # Render emoticons. if g.info["emoticons"]: g.info["rendered_body"] = Emoticons.render(g.info["rendered_body"]) elif action == "publish": # Publishing! Validate inputs first. invalid = False if len(g.info["body"]) == 0: invalid = True flash("You must enter a body for your blog post.") if len(g.info["subject"]) == 0: invalid = True flash("You must enter a subject for your blog post.") # Make sure the times are valid. date = None try: date = datetime.datetime( g.info["year"], g.info["month"], g.info["day"], g.info["hour"], g.info["min"], g.info["sec"], ) except ValueError as e: invalid = True flash("Invalid date/time: " + str(e)) # Format the categories. tags = [] for tag in g.info["categories"].split(","): tags.append(tag.strip()) # Okay to update? if invalid is False: # Convert the date into a Unix time stamp. epoch = float(date.strftime("%s")) new_id, new_fid = Blog.post_entry( post_id = g.info["post_id"], epoch = epoch, author = g.info["author"], subject = g.info["subject"], fid = g.info["fid"], avatar = g.info["avatar"], categories = tags, privacy = g.info["privacy"], ip = remote_addr(), emoticons = g.info["emoticons"], comments = g.info["comments"], format = g.info["format"], body = g.info["body"], ) return redirect(url_for(".entry", fid=new_fid)) if type(g.info["categories"]) is list: g.info["categories"] = ", ".join(g.info["categories"]) return template("blog/update.html")
def update(): """Post/edit a blog entry.""" # Get our available avatars. g.info["avatars"] = Blog.list_avatars() g.info["userpic"] = User.get_picture(uid=g.info["session"]["uid"]) # Default vars. g.info.update(dict( post_id="", fid="", author=g.info["session"]["uid"], subject="", body="", format="markdown", avatar="", categories="", privacy=Config.blog.default_privacy, sticky=False, emoticons=True, comments=Config.blog.allow_comments, preview=False, )) # Editing an existing post? post_id = request.args.get("id", request.form.get("id", None)) if post_id: post_id = Blog.resolve_id(post_id, drafts=True) if post_id: logger.info("Editing existing blog post {}".format(post_id)) post = Blog.get_entry(post_id) g.info["post_id"] = post_id g.info["post"] = post # Copy fields. for field in ["author", "fid", "subject", "time", "format", "body", "avatar", "categories", "privacy", "sticky", "emoticons", "comments"]: g.info[field] = post[field] # Are we SUBMITTING the form? if request.method == "POST": action = request.form.get("action") # Get all the fields from the posted params. g.info["post_id"] = request.form.get("id") for field in ["fid", "subject", "format", "body", "avatar", "categories", "privacy"]: g.info[field] = request.form.get(field) for boolean in ["sticky", "emoticons", "comments"]: g.info[boolean] = True if request.form.get(boolean, None) == "true" else False g.info["author"] = int(g.info["author"]) # What action are they doing? if action == "preview": g.info["preview"] = True # Render markdown? if g.info["format"] == "markdown": g.info["rendered_body"] = render_markdown(g.info["body"]) else: g.info["rendered_body"] = g.info["body"] # Render emoticons. if g.info["emoticons"]: g.info["rendered_body"] = Emoticons.render(g.info["rendered_body"]) elif action == "publish": # Publishing! Validate inputs first. invalid = False if len(g.info["body"]) == 0: invalid = True flash("You must enter a body for your blog post.") if len(g.info["subject"]) == 0: invalid = True flash("You must enter a subject for your blog post.") # Resetting the post's time stamp? if not request.form.get("id") or request.form.get("reset-time"): g.info["time"] = float(time.time()) else: g.info["time"] = float(request.form.get("time", time.time())) # Format the categories. tags = [] for tag in g.info["categories"].split(","): tags.append(tag.strip()) # Okay to update? if invalid is False: new_id, new_fid = Blog.post_entry( post_id = g.info["post_id"], epoch = g.info["time"], author = g.info["author"], subject = g.info["subject"], fid = g.info["fid"], avatar = g.info["avatar"], categories = tags, privacy = g.info["privacy"], ip = remote_addr(), emoticons = g.info["emoticons"], sticky = g.info["sticky"], comments = g.info["comments"], format = g.info["format"], body = g.info["body"], ) return redirect(url_for(".entry", fid=new_fid)) if type(g.info["categories"]) is list: g.info["categories"] = ", ".join(g.info["categories"]) return template("blog/update.html")