1.1 --- a/README.txt Mon Jan 12 20:02:24 2015 +0100
1.2 +++ b/README.txt Mon Jan 12 23:07:33 2015 +0100
1.3 @@ -28,10 +28,14 @@
1.4 Store details and published resources need to be accessible by the imip-agent
1.5 and www-data users:
1.6
1.7 - mkdir /var/lib/imip-agent/store /var/www/imip-agent/static
1.8 - chown imip-agent /var/lib/imip-agent/store /var/www/imip-agent/static
1.9 - chgrp www-data /var/lib/imip-agent/store /var/www/imip-agent/static
1.10 - chmod g+s /var/lib/imip-agent/store /var/www/imip-agent/static
1.11 + mkdir /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.12 + mkdir /var/www/imip-agent/static
1.13 + chown imip-agent /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.14 + chown imip-agent /var/www/imip-agent/static
1.15 + chgrp www-data /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.16 + chgrp www-data /var/www/imip-agent/static
1.17 + chmod g+ws /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.18 + chmod g+ws /var/www/imip-agent/static
1.19
1.20 Here, the setgid flag should ensure that new files and directories have the
1.21 appropriate group associated with them.
1.22 @@ -40,9 +44,12 @@
1.23
1.24 chown -R imip-agent /var/lib/imip-agent
1.25 chgrp -R imip-agent /var/lib/imip-agent
1.26 - chown -R imip-agent /var/lib/imip-agent/store /var/www/imip-agent/static
1.27 - chgrp -R www-data /var/lib/imip-agent/store /var/www/imip-agent/static
1.28 - chmod -R g+w /var/lib/imip-agent/store /var/www/imip-agent/static
1.29 + chown -R imip-agent /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.30 + chown -R imip-agent /var/www/imip-agent/static
1.31 + chgrp -R www-data /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.32 + chgrp -R www-data /var/www/imip-agent/static
1.33 + chmod -R g+w /var/lib/imip-agent/store /var/lib/imip-agent/preferences
1.34 + chmod -R g+w /var/www/imip-agent/static
1.35
1.36 Installing the Software
1.37 -----------------------
2.1 --- a/imip_manager.py Mon Jan 12 20:02:24 2015 +0100
2.2 +++ b/imip_manager.py Mon Jan 12 23:07:33 2015 +0100
2.3 @@ -24,7 +24,7 @@
2.4
2.5 LIBRARY_PATH = "/var/lib/imip-agent"
2.6
2.7 -import cgi, os, sys
2.8 +import cgi, os, sys, locale
2.9
2.10 sys.path.append(LIBRARY_PATH)
2.11
2.12 @@ -34,6 +34,7 @@
2.13 get_values, parse_object, to_part, to_timezone
2.14 from imiptools.mail import Messenger
2.15 from imiptools.period import have_conflict, get_slots, get_spans
2.16 +from imiptools.profile import Preferences
2.17 from vCalendar import to_node
2.18 import markup
2.19 import imip_store
2.20 @@ -155,6 +156,7 @@
2.21 self.env = CGIEnvironment()
2.22 user = self.env.get_user()
2.23 self.user = user and get_uri(user) or None
2.24 + self.preferences = None
2.25 self.requests = None
2.26
2.27 self.out = self.env.get_output()
2.28 @@ -193,6 +195,17 @@
2.29 self.requests = self.store.get_requests(self.user)
2.30 return self.requests
2.31
2.32 + # Preference methods.
2.33 +
2.34 + def set_user_locale(self):
2.35 + lang = self.get_preferences().get("LANG")
2.36 + locale.setlocale(locale.LC_TIME, lang or "C")
2.37 +
2.38 + def get_preferences(self):
2.39 + if not self.preferences:
2.40 + self.preferences = Preferences(self.user)
2.41 + return self.preferences
2.42 +
2.43 # Data management methods.
2.44
2.45 def remove_request(self, uid):
2.46 @@ -285,6 +298,8 @@
2.47 on the current page.
2.48 """
2.49
2.50 + self.set_user_locale()
2.51 +
2.52 details = self._get_details(obj)
2.53
2.54 # Provide a summary of the object.
2.55 @@ -292,9 +307,16 @@
2.56 self.page.dl()
2.57
2.58 for name in ["SUMMARY", "DTSTART", "DTEND", "ORGANIZER", "ATTENDEE"]:
2.59 - for value in get_values(details, name):
2.60 + if name in ["DTSTART", "DTEND"]:
2.61 + value, attr = get_item(details, name)
2.62 + tzid = attr.get("TZID")
2.63 + value = to_timezone(get_datetime(value), tzid).strftime("%c")
2.64 self.page.dt(name)
2.65 self.page.dd(value)
2.66 + else:
2.67 + for value in get_values(details, name):
2.68 + self.page.dt(name)
2.69 + self.page.dd(value)
2.70
2.71 self.page.dl.close()
2.72
2.73 @@ -317,8 +339,8 @@
2.74 for t in have_conflict(freebusy, [(dtstart, dtend)], True):
2.75 start, end, found_uid = t[:3]
2.76 if uid != found_uid:
2.77 - start = format_datetime(to_timezone(get_datetime(start), tzid))
2.78 - end = format_datetime(to_timezone(get_datetime(end), tzid))
2.79 + start = to_timezone(get_datetime(start), tzid).strftime("%c")
2.80 + end = to_timezone(get_datetime(end), tzid).strftime("%c")
2.81 self.page.p("Event conflicts with another from %s to %s." % (start, end))
2.82
2.83 def show_requests_on_page(self):
2.84 @@ -376,6 +398,8 @@
2.85
2.86 "Show the calendar for the current user."
2.87
2.88 + self.set_user_locale()
2.89 +
2.90 self.new_page(title="Calendar")
2.91 self.show_requests_on_page()
2.92
2.93 @@ -386,6 +410,11 @@
2.94 page.p("No events scheduled.")
2.95 return
2.96
2.97 + # Set the locale and obtain the user's timezone.
2.98 +
2.99 + prefs = self.get_preferences()
2.100 + tzid = prefs.get("TZID")
2.101 +
2.102 # Day view: start at the earliest known day and produce days until the
2.103 # latest known day, perhaps with expandable sections of empty days.
2.104
2.105 @@ -405,9 +434,11 @@
2.106 page.table(border=1, cellspacing=0, cellpadding=5)
2.107
2.108 for point, active in slots:
2.109 + dt = to_timezone(get_datetime(point), tzid or "UTC")
2.110 +
2.111 page.tr()
2.112 page.th(class_="timeslot")
2.113 - page.add(point)
2.114 + page.add(dt.strftime("%c"))
2.115 page.th.close()
2.116
2.117 for t in active:
3.1 --- a/imip_store.py Mon Jan 12 20:02:24 2015 +0100
3.2 +++ b/imip_store.py Mon Jan 12 23:07:33 2015 +0100
3.3 @@ -21,19 +21,11 @@
3.4
3.5 from datetime import datetime
3.6 from imiptools.config import STORE_DIR, PUBLISH_DIR
3.7 -from os.path import abspath, commonprefix, exists, isfile, join, split
3.8 -from os import chmod, listdir, makedirs
3.9 +from imiptools.filesys import fix_permissions, FileBase
3.10 +from os.path import exists, isfile, join
3.11 +from os import listdir
3.12 from vCalendar import iterwrite
3.13
3.14 -def check_dir(base, dir):
3.15 - return commonprefix([base, abspath(dir)]) == base
3.16 -
3.17 -def fix_permissions(filename):
3.18 - try:
3.19 - chmod(filename, 0660)
3.20 - except OSError:
3.21 - pass
3.22 -
3.23 def make_calendar(fragment, method=None):
3.24
3.25 """
3.26 @@ -50,44 +42,13 @@
3.27 def to_stream(out, fragment, encoding="utf-8"):
3.28 iterwrite(out, encoding=encoding).append(fragment)
3.29
3.30 -class FileBase:
3.31 -
3.32 - "Basic filesystem operations."
3.33 -
3.34 - def __init__(self, store_dir=STORE_DIR):
3.35 - self.store_dir = store_dir
3.36 - if not exists(self.store_dir):
3.37 - makedirs(self.store_dir)
3.38 -
3.39 - def get_file_object(self, base, *parts):
3.40 - pathname = join(base, *parts)
3.41 - return check_dir(base, pathname) and pathname or None
3.42 -
3.43 - def get_object_in_store(self, *parts):
3.44 -
3.45 - """
3.46 - Return the name of any valid object stored within a hierarchy specified
3.47 - by the given 'parts'.
3.48 - """
3.49 -
3.50 - parent = expected = self.store_dir
3.51 -
3.52 - for part in parts:
3.53 - filename = self.get_file_object(expected, part)
3.54 - if not filename:
3.55 - return False
3.56 - parent = expected
3.57 - expected = filename
3.58 -
3.59 - if not exists(parent):
3.60 - makedirs(parent)
3.61 -
3.62 - return filename
3.63 -
3.64 class FileStore(FileBase):
3.65
3.66 "A file store of tabular free/busy data and objects."
3.67
3.68 + def __init__(self, store_dir=STORE_DIR):
3.69 + FileBase.__init__(self, store_dir)
3.70 +
3.71 def get_events(self, user):
3.72
3.73 "Return a list of event identifiers."
4.1 --- a/imiptools/config.py Mon Jan 12 20:02:24 2015 +0100
4.2 +++ b/imiptools/config.py Mon Jan 12 23:07:33 2015 +0100
4.3 @@ -18,6 +18,15 @@
4.4
4.5 PUBLISH_DIR = "/var/www/imip-agent/static"
4.6
4.7 +# The location of user preferences information.
4.8 +
4.9 +PREFERENCES_DIR = "/var/lib/imip-agent/preferences"
4.10 +
4.11 +# Permissions for files.
4.12 +# This is meant to ensure that both the agent and Web users can access files.
4.13 +
4.14 +DEFAULT_PERMISSIONS = 0660
4.15 +
4.16 # The published location of the manager application.
4.17 # This must match any Web site configuration details for the manager.
4.18
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/imiptools/filesys.py Mon Jan 12 23:07:33 2015 +0100
5.3 @@ -0,0 +1,69 @@
5.4 +#!/usr/bin/env python
5.5 +
5.6 +"""
5.7 +Filesystem utilities.
5.8 +
5.9 +Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>
5.10 +
5.11 +This program is free software; you can redistribute it and/or modify it under
5.12 +the terms of the GNU General Public License as published by the Free Software
5.13 +Foundation; either version 3 of the License, or (at your option) any later
5.14 +version.
5.15 +
5.16 +This program is distributed in the hope that it will be useful, but WITHOUT
5.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
5.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
5.19 +details.
5.20 +
5.21 +You should have received a copy of the GNU General Public License along with
5.22 +this program. If not, see <http://www.gnu.org/licenses/>.
5.23 +"""
5.24 +
5.25 +from imiptools.config import DEFAULT_PERMISSIONS
5.26 +from os.path import abspath, commonprefix, exists, join
5.27 +from os import chmod, makedirs
5.28 +
5.29 +def check_dir(base, dir):
5.30 + return commonprefix([base, abspath(dir)]) == base
5.31 +
5.32 +def fix_permissions(filename):
5.33 + try:
5.34 + chmod(filename, DEFAULT_PERMISSIONS)
5.35 + except OSError:
5.36 + pass
5.37 +
5.38 +class FileBase:
5.39 +
5.40 + "Basic filesystem operations."
5.41 +
5.42 + def __init__(self, store_dir):
5.43 + self.store_dir = store_dir
5.44 + if not exists(self.store_dir):
5.45 + makedirs(self.store_dir)
5.46 +
5.47 + def get_file_object(self, base, *parts):
5.48 + pathname = join(base, *parts)
5.49 + return check_dir(base, pathname) and pathname or None
5.50 +
5.51 + def get_object_in_store(self, *parts):
5.52 +
5.53 + """
5.54 + Return the name of any valid object stored within a hierarchy specified
5.55 + by the given 'parts'.
5.56 + """
5.57 +
5.58 + parent = expected = self.store_dir
5.59 +
5.60 + for part in parts:
5.61 + filename = self.get_file_object(expected, part)
5.62 + if not filename:
5.63 + return False
5.64 + parent = expected
5.65 + expected = filename
5.66 +
5.67 + if not exists(parent):
5.68 + makedirs(parent)
5.69 +
5.70 + return filename
5.71 +
5.72 +# vim: tabstop=4 expandtab shiftwidth=4
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/imiptools/profile.py Mon Jan 12 23:07:33 2015 +0100
6.3 @@ -0,0 +1,66 @@
6.4 +#!/usr/bin/env python
6.5 +
6.6 +"""
6.7 +User profile management.
6.8 +
6.9 +Copyright (C) 2015 Paul Boddie <paul@boddie.org.uk>
6.10 +
6.11 +This program is free software; you can redistribute it and/or modify it under
6.12 +the terms of the GNU General Public License as published by the Free Software
6.13 +Foundation; either version 3 of the License, or (at your option) any later
6.14 +version.
6.15 +
6.16 +This program is distributed in the hope that it will be useful, but WITHOUT
6.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
6.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
6.19 +details.
6.20 +
6.21 +You should have received a copy of the GNU General Public License along with
6.22 +this program. If not, see <http://www.gnu.org/licenses/>.
6.23 +"""
6.24 +
6.25 +from imiptools.config import PREFERENCES_DIR
6.26 +from imiptools.filesys import fix_permissions, FileBase
6.27 +from os.path import exists, isdir
6.28 +from os import makedirs
6.29 +
6.30 +class Preferences(FileBase):
6.31 +
6.32 + "A simple preferences file manager."
6.33 +
6.34 + def __init__(self, user, store_dir=PREFERENCES_DIR):
6.35 + FileBase.__init__(self, store_dir)
6.36 + self.user = user
6.37 +
6.38 + def get(self, name, default=None):
6.39 + try:
6.40 + return self[name]
6.41 + except KeyError:
6.42 + return default
6.43 +
6.44 + def __getitem__(self, name):
6.45 + filename = self.get_object_in_store(self.user, name)
6.46 + if not filename or not exists(filename):
6.47 + raise KeyError, name
6.48 +
6.49 + f = open(filename)
6.50 + try:
6.51 + return f.read().strip()
6.52 + finally:
6.53 + f.close()
6.54 +
6.55 + def __setitem__(self, name, value):
6.56 + filename = self.get_object_in_store(self.user, name)
6.57 + if not filename:
6.58 + return False
6.59 +
6.60 + f = open(filename, "w")
6.61 + try:
6.62 + f.write(value)
6.63 + finally:
6.64 + f.close()
6.65 + fix_permissions(filename)
6.66 +
6.67 + return True
6.68 +
6.69 +# vim: tabstop=4 expandtab shiftwidth=4