# HG changeset patch # User Paul Boddie # Date 1421100453 -3600 # Node ID ee2bd0ee8f552b78ca0ee239e03a1a73c75b3768 # Parent 367b6c1b82b0ad082ed184834aa5055caadacdcc Moved common filesystem functionality into a new module. Added preferences support so that the timezone and locale associated with individual users can be retrieved and used to configure the management interface. Made the default permissions of stored data a configuration setting. diff -r 367b6c1b82b0 -r ee2bd0ee8f55 README.txt --- a/README.txt Mon Jan 12 20:02:24 2015 +0100 +++ b/README.txt Mon Jan 12 23:07:33 2015 +0100 @@ -28,10 +28,14 @@ Store details and published resources need to be accessible by the imip-agent and www-data users: - mkdir /var/lib/imip-agent/store /var/www/imip-agent/static - chown imip-agent /var/lib/imip-agent/store /var/www/imip-agent/static - chgrp www-data /var/lib/imip-agent/store /var/www/imip-agent/static - chmod g+s /var/lib/imip-agent/store /var/www/imip-agent/static + mkdir /var/lib/imip-agent/store /var/lib/imip-agent/preferences + mkdir /var/www/imip-agent/static + chown imip-agent /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chown imip-agent /var/www/imip-agent/static + chgrp www-data /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chgrp www-data /var/www/imip-agent/static + chmod g+ws /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chmod g+ws /var/www/imip-agent/static Here, the setgid flag should ensure that new files and directories have the appropriate group associated with them. @@ -40,9 +44,12 @@ chown -R imip-agent /var/lib/imip-agent chgrp -R imip-agent /var/lib/imip-agent - chown -R imip-agent /var/lib/imip-agent/store /var/www/imip-agent/static - chgrp -R www-data /var/lib/imip-agent/store /var/www/imip-agent/static - chmod -R g+w /var/lib/imip-agent/store /var/www/imip-agent/static + chown -R imip-agent /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chown -R imip-agent /var/www/imip-agent/static + chgrp -R www-data /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chgrp -R www-data /var/www/imip-agent/static + chmod -R g+w /var/lib/imip-agent/store /var/lib/imip-agent/preferences + chmod -R g+w /var/www/imip-agent/static Installing the Software ----------------------- diff -r 367b6c1b82b0 -r ee2bd0ee8f55 imip_manager.py --- a/imip_manager.py Mon Jan 12 20:02:24 2015 +0100 +++ b/imip_manager.py Mon Jan 12 23:07:33 2015 +0100 @@ -24,7 +24,7 @@ LIBRARY_PATH = "/var/lib/imip-agent" -import cgi, os, sys +import cgi, os, sys, locale sys.path.append(LIBRARY_PATH) @@ -34,6 +34,7 @@ get_values, parse_object, to_part, to_timezone from imiptools.mail import Messenger from imiptools.period import have_conflict, get_slots, get_spans +from imiptools.profile import Preferences from vCalendar import to_node import markup import imip_store @@ -155,6 +156,7 @@ self.env = CGIEnvironment() user = self.env.get_user() self.user = user and get_uri(user) or None + self.preferences = None self.requests = None self.out = self.env.get_output() @@ -193,6 +195,17 @@ self.requests = self.store.get_requests(self.user) return self.requests + # Preference methods. + + def set_user_locale(self): + lang = self.get_preferences().get("LANG") + locale.setlocale(locale.LC_TIME, lang or "C") + + def get_preferences(self): + if not self.preferences: + self.preferences = Preferences(self.user) + return self.preferences + # Data management methods. def remove_request(self, uid): @@ -285,6 +298,8 @@ on the current page. """ + self.set_user_locale() + details = self._get_details(obj) # Provide a summary of the object. @@ -292,9 +307,16 @@ self.page.dl() for name in ["SUMMARY", "DTSTART", "DTEND", "ORGANIZER", "ATTENDEE"]: - for value in get_values(details, name): + if name in ["DTSTART", "DTEND"]: + value, attr = get_item(details, name) + tzid = attr.get("TZID") + value = to_timezone(get_datetime(value), tzid).strftime("%c") self.page.dt(name) self.page.dd(value) + else: + for value in get_values(details, name): + self.page.dt(name) + self.page.dd(value) self.page.dl.close() @@ -317,8 +339,8 @@ for t in have_conflict(freebusy, [(dtstart, dtend)], True): start, end, found_uid = t[:3] if uid != found_uid: - start = format_datetime(to_timezone(get_datetime(start), tzid)) - end = format_datetime(to_timezone(get_datetime(end), tzid)) + start = to_timezone(get_datetime(start), tzid).strftime("%c") + end = to_timezone(get_datetime(end), tzid).strftime("%c") self.page.p("Event conflicts with another from %s to %s." % (start, end)) def show_requests_on_page(self): @@ -376,6 +398,8 @@ "Show the calendar for the current user." + self.set_user_locale() + self.new_page(title="Calendar") self.show_requests_on_page() @@ -386,6 +410,11 @@ page.p("No events scheduled.") return + # Set the locale and obtain the user's timezone. + + prefs = self.get_preferences() + tzid = prefs.get("TZID") + # Day view: start at the earliest known day and produce days until the # latest known day, perhaps with expandable sections of empty days. @@ -405,9 +434,11 @@ page.table(border=1, cellspacing=0, cellpadding=5) for point, active in slots: + dt = to_timezone(get_datetime(point), tzid or "UTC") + page.tr() page.th(class_="timeslot") - page.add(point) + page.add(dt.strftime("%c")) page.th.close() for t in active: diff -r 367b6c1b82b0 -r ee2bd0ee8f55 imip_store.py --- a/imip_store.py Mon Jan 12 20:02:24 2015 +0100 +++ b/imip_store.py Mon Jan 12 23:07:33 2015 +0100 @@ -21,19 +21,11 @@ from datetime import datetime from imiptools.config import STORE_DIR, PUBLISH_DIR -from os.path import abspath, commonprefix, exists, isfile, join, split -from os import chmod, listdir, makedirs +from imiptools.filesys import fix_permissions, FileBase +from os.path import exists, isfile, join +from os import listdir from vCalendar import iterwrite -def check_dir(base, dir): - return commonprefix([base, abspath(dir)]) == base - -def fix_permissions(filename): - try: - chmod(filename, 0660) - except OSError: - pass - def make_calendar(fragment, method=None): """ @@ -50,44 +42,13 @@ def to_stream(out, fragment, encoding="utf-8"): iterwrite(out, encoding=encoding).append(fragment) -class FileBase: - - "Basic filesystem operations." - - def __init__(self, store_dir=STORE_DIR): - self.store_dir = store_dir - if not exists(self.store_dir): - makedirs(self.store_dir) - - def get_file_object(self, base, *parts): - pathname = join(base, *parts) - return check_dir(base, pathname) and pathname or None - - def get_object_in_store(self, *parts): - - """ - Return the name of any valid object stored within a hierarchy specified - by the given 'parts'. - """ - - parent = expected = self.store_dir - - for part in parts: - filename = self.get_file_object(expected, part) - if not filename: - return False - parent = expected - expected = filename - - if not exists(parent): - makedirs(parent) - - return filename - class FileStore(FileBase): "A file store of tabular free/busy data and objects." + def __init__(self, store_dir=STORE_DIR): + FileBase.__init__(self, store_dir) + def get_events(self, user): "Return a list of event identifiers." diff -r 367b6c1b82b0 -r ee2bd0ee8f55 imiptools/config.py --- a/imiptools/config.py Mon Jan 12 20:02:24 2015 +0100 +++ b/imiptools/config.py Mon Jan 12 23:07:33 2015 +0100 @@ -18,6 +18,15 @@ PUBLISH_DIR = "/var/www/imip-agent/static" +# The location of user preferences information. + +PREFERENCES_DIR = "/var/lib/imip-agent/preferences" + +# Permissions for files. +# This is meant to ensure that both the agent and Web users can access files. + +DEFAULT_PERMISSIONS = 0660 + # The published location of the manager application. # This must match any Web site configuration details for the manager. diff -r 367b6c1b82b0 -r ee2bd0ee8f55 imiptools/filesys.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/filesys.py Mon Jan 12 23:07:33 2015 +0100 @@ -0,0 +1,69 @@ +#!/usr/bin/env python + +""" +Filesystem utilities. + +Copyright (C) 2014, 2015 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from imiptools.config import DEFAULT_PERMISSIONS +from os.path import abspath, commonprefix, exists, join +from os import chmod, makedirs + +def check_dir(base, dir): + return commonprefix([base, abspath(dir)]) == base + +def fix_permissions(filename): + try: + chmod(filename, DEFAULT_PERMISSIONS) + except OSError: + pass + +class FileBase: + + "Basic filesystem operations." + + def __init__(self, store_dir): + self.store_dir = store_dir + if not exists(self.store_dir): + makedirs(self.store_dir) + + def get_file_object(self, base, *parts): + pathname = join(base, *parts) + return check_dir(base, pathname) and pathname or None + + def get_object_in_store(self, *parts): + + """ + Return the name of any valid object stored within a hierarchy specified + by the given 'parts'. + """ + + parent = expected = self.store_dir + + for part in parts: + filename = self.get_file_object(expected, part) + if not filename: + return False + parent = expected + expected = filename + + if not exists(parent): + makedirs(parent) + + return filename + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 367b6c1b82b0 -r ee2bd0ee8f55 imiptools/profile.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/profile.py Mon Jan 12 23:07:33 2015 +0100 @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +User profile management. + +Copyright (C) 2015 Paul Boddie + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 3 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +""" + +from imiptools.config import PREFERENCES_DIR +from imiptools.filesys import fix_permissions, FileBase +from os.path import exists, isdir +from os import makedirs + +class Preferences(FileBase): + + "A simple preferences file manager." + + def __init__(self, user, store_dir=PREFERENCES_DIR): + FileBase.__init__(self, store_dir) + self.user = user + + def get(self, name, default=None): + try: + return self[name] + except KeyError: + return default + + def __getitem__(self, name): + filename = self.get_object_in_store(self.user, name) + if not filename or not exists(filename): + raise KeyError, name + + f = open(filename) + try: + return f.read().strip() + finally: + f.close() + + def __setitem__(self, name, value): + filename = self.get_object_in_store(self.user, name) + if not filename: + return False + + f = open(filename, "w") + try: + f.write(value) + finally: + f.close() + fix_permissions(filename) + + return True + +# vim: tabstop=4 expandtab shiftwidth=4