# HG changeset patch # User Paul Boddie # Date 1461334978 -7200 # Node ID 52476453b1e3b9ab818cd11499b5bfb34b94277d # Parent 35958ffd9f836627dd06d0708ddba1bc483bf3b0# Parent 008b80c604daea1c40ca6d54b56862284674b7c4 Merged freebusy-collections into the default branch. diff -r 35958ffd9f83 -r 52476453b1e3 conf/postgresql/schema.sql --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conf/postgresql/schema.sql Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,163 @@ +-- Object store tables. + +create table objects ( + store_user varchar not null, + object_uid varchar not null, + object_text varchar not null, + status varchar not null, -- 'active', 'cancelled' + primary key(store_user, object_uid) +); + +create table countered_objects ( + store_user varchar not null, + other varchar not null, + object_uid varchar not null, + object_text varchar not null, + primary key(store_user, object_uid) +); + +create table recurrences ( + store_user varchar not null, + object_uid varchar not null, + object_recurrenceid varchar not null, + object_text varchar not null, + status varchar not null, -- 'active', 'cancelled' + primary key(store_user, object_uid, object_recurrenceid) +); + +create table countered_recurrences ( + store_user varchar not null, + other varchar not null, + object_uid varchar not null, + object_recurrenceid varchar not null, + object_text varchar not null, + primary key(store_user, object_uid, object_recurrenceid) +); + +-- Object store free/busy details. + +create table freebusy ( + store_user varchar not null, + "start" varchar not null, + "end" varchar not null, + object_uid varchar, + transp varchar, + object_recurrenceid varchar, + summary varchar, + organiser varchar, + expires varchar +); + +create index freebusy_start on freebusy(store_user, "start"); +create index freebusy_end on freebusy(store_user, "end"); + +create table freebusy_offers ( + store_user varchar not null, + "start" varchar not null, + "end" varchar not null, + object_uid varchar, + transp varchar, + object_recurrenceid varchar, + summary varchar, + organiser varchar, + expires varchar +); + +create index freebusy_offers_start on freebusy_offers(store_user, "start"); +create index freebusy_offers_end on freebusy_offers(store_user, "end"); + +create table freebusy_other ( + store_user varchar not null, + other varchar not null, + "start" varchar not null, + "end" varchar not null, + object_uid varchar, + transp varchar, + object_recurrenceid varchar, + summary varchar, + organiser varchar, + expires varchar +); + +create index freebusy_other_start on freebusy_other(store_user, other, "start"); +create index freebusy_other_end on freebusy_other(store_user, other, "end"); + +create table freebusy_providers ( + store_user varchar not null, + object_uid varchar not null, + object_recurrenceid varchar +); + +create index freebusy_providers_store_user on freebusy_providers(store_user); + +create table freebusy_provider_datetimes ( + store_user varchar not null, + "start" varchar +); + +create index freebusy_provider_datetimes_store_user on freebusy_provider_datetimes(store_user); + +-- Object store request details. + +create table requests ( + store_user varchar not null, + object_uid varchar not null, + object_recurrenceid varchar, + request_type varchar +); + +create index requests_object_uid on requests(store_user, object_uid); + + + +-- Journal store tables. + +-- Journal free/busy details. + +create table quota_freebusy ( + quota varchar not null, + user_group varchar not null, + "start" varchar not null, + "end" varchar not null, + object_uid varchar, + transp varchar, + object_recurrenceid varchar, + summary varchar, + organiser varchar, + expires varchar +); + +create index quota_freebusy_start on quota_freebusy(quota, user_group, "start"); +create index quota_freebusy_end on quota_freebusy(quota, user_group, "end"); + +create table user_freebusy ( + quota varchar not null, + store_user varchar not null, + "start" varchar not null, + "end" varchar not null, + object_uid varchar, + transp varchar, + object_recurrenceid varchar, + summary varchar, + organiser varchar, + expires varchar +); + +create index user_freebusy_start on user_freebusy(quota, store_user, "start"); +create index user_freebusy_end on user_freebusy(quota, store_user, "end"); + +-- Journal user groups and limits. + +create table quota_limits ( + quota varchar not null, + user_group varchar not null, + quota_limit varchar not null, + primary key(user_group) +); + +create table user_groups ( + quota varchar not null, + store_user varchar not null, + user_group varchar not null, + primary key(store_user, user_group) +); diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/Administration --- a/docs/wiki/Administration Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/Administration Fri Apr 22 16:22:58 2016 +0200 @@ -131,3 +131,38 @@ use of this tool will be performed by the packaging system provided by an operating system distribution. The `tools/install.sh` script runs the above tool as part of the installation process. + +== Copying Stores and Changing Store Types == + +A rudimentary tool is provided that can copy data between stores, even those of +different types, thus allowing the migration of data from one kind of store to +another. Although it does not perform the copying in the most efficient manner, +it provides a convenient method of copying that uses the software's own general +interfaces for store access and thus acts as a way of verifying that these are +functioning correctly. + +To copy a configured store to another filesystem location: + +{{{ +tools/copy_store.py -t file /tmp/store /tmp/journal +}}} + +To copy a configured store to a database (which must have been initialised): + +{{{ +tools/copy_store.py -t postgresql 'dbname=store' 'dbname=journal' +}}} + +To copy an explicitly-specified file store to another filesystem location: + +{{{ +tools/copy_store.py \ + -f file /var/lib/imip-agent/store /var/lib/imip-agent/journal \ + -t file /tmp/store /tmp/journal +}}} + +The help message for the tool provides general guidance for its use: + +{{{ +tools/copy_store.py --help +}}} diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/Configuration --- a/docs/wiki/Configuration Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/Configuration Fri Apr 22 16:22:58 2016 +0200 @@ -95,11 +95,15 @@ === System-Level and Tool Configuration === -Given a choice of [[../SystemUsers|system users and groups]], and the choices -made when configuring how imip-agent integrates with other components, the -resulting configuration must be indicated in the `config.sh` file. Since -the `tools/install.sh` script depends on this configuration, changes must -be made to the file in the `tools/config.sh` location before installation +The `config.sh` file must indicate choices in the following areas: + + * The [[../DataStore|data store]] type to be employed + + * The [[../SystemUsers|system users and groups]] involved with running the + software and storing data + +Since the `tools/install.sh` script depends on this configuration, changes +must be made to the file in the `tools/config.sh` location before installation can occur. {{{#!table diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/DataStore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/wiki/DataStore Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,17 @@ += Data Store = + +The data store for imip-agent holds calendar data and free/busy information. +The following data store types exist: + + * A [[../FileStore|file store]] employing textual files in the filesystem + + * A [[../DatabaseStore|database store]] employing database tables managed + by a database management system + +For simplicity, the file store is the default storage mechanism for imip-agent, +but the database store is provided as an alternative where different operating +characteristics are desired. + +The [[../Configuration|configuration]] files (`config.sh` and `config.py`) need +updating to reflect the choice of data store, with the directory parameters +set to appropriate values for the chosen store type. diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/DatabaseStore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/wiki/DatabaseStore Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,122 @@ += Database Store = + +The database data store offers a mechanism for storing calendar objects and free/busy +details in a database system, currently focusing on relational database systems, with +specific support for PostgreSQL. + +Benefits of the database store include convenient and standardised interfaces for +querying and inspecting the data, together with various guarantees around the durability +of the stored data. However, more administrative knowledge is usually required to operate +database installations satisfactorily, and activities such as archiving require extra +planning and expertise. + +The [[../FileStore|file store]] offers a more convenient method of managing calendar +data, albeit offering arguably less scalability and less convenience when data volumes +become significantly large. + +== Configuration Settings == + +The [[../Configuration|configuration]] files (`config.sh` and `config.py`) need to be +updated when choosing a database store. + +{{{#!table +'''File''' || '''Setting''' || '''Value''' || '''Description''' +== +`config.sh` +|| `STORE_TYPE` +|| `postgresql` +|| Selects a specific database store type; currently, only `postgresql` + .. is supported +== +`config.py` +== + `config.py` +|| `STORE_DIR` +|| `dbname=imip_agent` +|| Indicates a connection string for the store database +== +`JOURNAL_DIR` +|| `dbname=imip_agent` +|| Indicates a connection string for the journal database +}}} + +== Journal Structure == + +The journal information is retained in a collection of tables. Unlike the structure of +the [[../FileStore|file store]] in the [[../FilesystemUsage|filesystem]], each table +contains details for all quota groups, with each query indicating the group to select +the appropriate information. + +{{{#!table +'''Table''' || '''Purpose''' +== +`quota_freebusy` +|| Period descriptions describing reservations for resources sharing a quota (`quota`) +.. made by users or groups (`user_group`), structured similarly to the `freebusy` table +.. in the data store +== +`quota_limits` +|| A mapping from user identities or group identifiers to quota limits +== +`user_freebusy` +|| Period descriptions for reservations made in the context of a quota (`quota`) by a +.. specific user (`store_user`), structured similarly to the `freebusy` table in the +.. data store +== +`user_groups` +|| A mapping from user identities to group identifiers indicating the sharing of a quota +.. across a number of users +}}} + +== Store Structure == + +The store information is retained in a collection of tables. Unlike the structure of +the [[../FileStore|file store]] in the [[../FilesystemUsage|filesystem]], each table +contains details for all users, with each query indicating the user to select +the appropriate information. + +{{{#!table +'''Table''' || '''Purpose''' +== +`countered_objects` +|| Retains counter-proposals corresponding to events held in the `objects` table +.. received by each user (`store_user`) from another user (`other`) +== +`countered_recurrences` +|| Retains counter-proposals corresponding to events held in the `recurrences` table +.. received by each user (`store_user`) from another user (`other`) +== +`freebusy` +|| Period descriptions indicating start and end datetimes (in UTC), unique identifier +.. (`object_uid`), transparency, recurrence identifier (`object_recurrenceid`), +.. summary and organiser, reflecting the schedule of each user (`store_user`) +== +`freebusy_offers` +|| Period descriptions for scheduling offers made by counter-proposals sent by each +.. user (`store_user`) +== +`freebusy_other` +|| Period descriptions received or deduced for a user (`store_user`) structured +.. similarly to the user's own `freebusy` records +== +`freebusy_providers` +|| Details of [[../EventRecurrences|recurring events]] for which new free/busy records +.. must be [[../CronIntegration|periodically generated]] because these events recur +.. indefinitely, selectable for each user (`store_user`) +== +`freebusy_provider_datetimes` +|| Date/time details associated with the `freebusy_providers` information +== +`objects` +|| Records for each user (`store_data`) containing received event data (`object_text`) +== +`requests` +|| A collections of records, each belonging to a specific user (`store_user`) +.. consisting of a unique identifier (`object_uid`), normalised recurrence identifier +.. (`object_recurrenceid`) if appropriate, and (optionally) a scheduling method, +.. indicating the availability of an incoming scheduling request for handling by a user +== +`recurrences` +|| Records for each user (`store_data`) containing received recurrence event data +.. (`object_text`) +}}} diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/FileStore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/wiki/FileStore Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,40 @@ += File Store = + +The file data store is the default mechanism for storing calendar objects and +free/busy details, making use of various directories as described in the +[[../FilesystemUsage|filesystem usage guide]]. + +Benefits of the file store include transparency and ease of administration: +all data is stored in text files, direct modification of certain files can be +performed to change the system's behaviour, archiving is possible using +traditional filesystem tools. However, the simple representation may make +certain operations costly, such as the modification of tabular data, and +querying of data may not always be particularly convenient. + +Thus, the [[../DatabaseStore|database store]] exists as an alternative, offering +different characteristics to those of the file store. + +== Configuration Settings == + +The [[../Configuration|configuration]] files (`config.sh` and `config.py`) need to be +updated when choosing a file store. + +{{{#!table +'''File''' || '''Setting''' || '''Value''' || '''Description''' +== +`config.sh` +|| `STORE_TYPE` +|| `file` +|| Selects the default file storage type +== +`config.py` +== + `config.py` +|| `STORE_DIR` +|| `/var/lib/imip-agent/store` +|| Indicates the filesystem location of the data store +== +`JOURNAL_DIR` +|| `/var/lib/imip-agent/journal` +|| Indicates the filesystem location of the journal +}}} diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/FilesystemUsage --- a/docs/wiki/FilesystemUsage Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/FilesystemUsage Fri Apr 22 16:22:58 2016 +0200 @@ -26,6 +26,11 @@ Note that the free/busy resources are located in `/var/www` as opposed to `/var/lib` since they are intended to be published on the Web. +Meanwhile, the journal and store resources are only present in the filesystem +if the [[../FileStore|file store]] is in use. Where a +[[../DatabaseStore|database store]] is being used instead, such resources are +located in a database system. + == Journal Structure == Within the journal directory are a collection of subdirectories, each of which diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/FrontPage --- a/docs/wiki/FrontPage Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/FrontPage Fri Apr 22 16:22:58 2016 +0200 @@ -165,6 +165,7 @@ * [[/CalendaringSupport|Calendaring Support]] * [[/CounterProposals|Counter-Proposals and Offers]] * [[/CronIntegration|Cron Task Scheduler Integration]] + * [[/DataStore|Data Store]] * [[/EventRecurrences|Event Recurrences]] * [[/FilesystemUsage|Filesystem Usage]] * [[/IncomingMessages|Incoming Messages]] diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/GettingStarted --- a/docs/wiki/GettingStarted Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/GettingStarted Fri Apr 22 16:22:58 2016 +0200 @@ -101,6 +101,10 @@ || Postfix routing and transport configuration || [[../MailIntegration|E-Mail Integration]] and .. [[../MailboxIntegration|Mailbox Integration]] +== +`postgresql` +|| PostgreSQL configuration +|| [[../DatabaseStore|Database Store]] }}} In addition, a `tools` directory provides a configuration helper tool diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/MailIntegration--MTA --- a/docs/wiki/MailIntegration--MTA Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/MailIntegration--MTA Fri Apr 22 16:22:58 2016 +0200 @@ -77,3 +77,32 @@ mailserver:example.com }}} }}}} + +== Useful Commands == + +The following commands prove useful when troubleshooting and appear to be +available as shown within a Debian environment. + +{{{#!table +'''Task''' || '''Exim''' || '''Postfix''' +== +Check the mail queue +|| `mailq` +== +Process the mail queue +|| `sendmail -q` (or `exim -q` or `runq`) +|| `sendmail -q` (or `postqueue`) +== +Flush the mail queue +|| `exim -qff` +|| `postqueue -f` +== +Deliver a specific message +|| `exim -M ` +|| `postqueue -i ` +== +Test delivery for an address +|| `sendmail -bt
` (see also `sendmail -v -bv
` and `sendmail -v -bvs
`) +}}} + +See [[http://bradthemad.org/tech/notes/exim_cheatsheet.php|Exim Cheatsheet]] and [[http://www.postfix.org/DEBUG_README.html|Postfix Debugging Howto]] for more guidance. diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/Prerequisites --- a/docs/wiki/Prerequisites Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/Prerequisites Fri Apr 22 16:22:58 2016 +0200 @@ -11,6 +11,11 @@ Python:: python pytz:: python-tz +If the [[../DatabaseStore|database store]] is used, the following packages are required: + + PostgreSQL:: postgresql-9.4 (version may vary between releases) + psycopg2:: python-psycopg2 + To provide localised messages: gettext:: gettext diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/Resources --- a/docs/wiki/Resources Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/Resources Fri Apr 22 16:22:58 2016 +0200 @@ -372,7 +372,8 @@ ==== Initialising Quotas ==== -Within the journal storage area (described in the [[../FilesystemUsage|filesystem guide]]), +Within the journal storage area (described in the +[[../FilesystemUsage|filesystem guide]] and [[../DatabaseStore|database guide]]), a quota group directory must be initialised with a `limits` file indicating the amount of time that can be occupied by the cumulative total of all events scheduled by an individual user or a group of which they are a member. For @@ -395,6 +396,35 @@ particular user will be unable to reserve the resource unless defined as a member of a group listed in the `limits` file, as described below. +{{{{#!wiki tip +In a deployment using the [[../FileStore|file store]], files as described +above and below hold mappings and definitions in the given format. In a +deployment using the [[../DatabaseStore|database store]], database tables +hold such mappings with each column dedicated to a particular kind of +information. + +The examples here can be transcribed by just taking each +element and putting it in the appropriate column within a table, making +sure to set the `quota` column to indicate which quota is involved. For +example, to set the above limits in PostgreSQL, the following operations +may be used: + +{{{ +insert into quota_limits (quota, user_group, quota_limit) values ( + 'mailto:resource-car-cadillac@example.com', + 'mailto:vincent.vole@example.com', + 'PT10H'); +insert into quota_limits (quota, user_group, quota_limit) values ( + 'mailto:resource-car-cadillac@example.com', + '*', + 'PT10H'); +}}} + +Here, the `quota` column is set to `mailto:resource-car-cadillac@example.com`. +In a file-based journal, the equivalent `limits` file would be placed within a +quota directory having the name `mailto:resource-car-cadillac@example.com`. +}}}} + ==== Sharing Quotas Across Users ==== When the use of resources is to be shared between users in such a way that diff -r 35958ffd9f83 -r 52476453b1e3 docs/wiki/Testing --- a/docs/wiki/Testing Tue Apr 19 21:20:57 2016 +0200 +++ b/docs/wiki/Testing Fri Apr 22 16:22:58 2016 +0200 @@ -90,6 +90,27 @@ are presented with message content, and testing for the desired effects of running those programs with such content. +{{{ +./test_all.sh +}}} + +The test suite by default (or by indicating `file` as the data store type), +records test information in subdirectories of `/tmp`. + +To run all tests against a different data store, such as a +[[../DatabaseStore|database store]] instead of the [[../FileStore|file store]], +the `STORE_TYPE` environment variable can be specified as in the following +example: + +{{{ +STORE_TYPE=postgresql ./test_all.sh +}}} + +The test suite records `postgresql` tests in a specially-created database +called `imip_agent_test` that the system user running the tests must be allowed +to create and drop. This may require the granting of administrative rights +within PostgreSQL for the system user concerned. + Individual tests may also be run directly from the topmost level of the source code distribution. For example: @@ -104,3 +125,50 @@ output from various commands from the last test script invocation; the `err.tmp` will contain tracebacks indicating serious error conditions, should any have occurred. + +== Testing in the Deployment Environment == + +Although the above testing may indicate that the software is functional, +it does not demonstrate that the software has been successfully integrated +into the deployment environment. One elementary test involves sending mail +to an address that should be configured to handle incoming calendar messages. + +A basic script is provided that replicates a subset of the functionality in +the traditional `mail` command for sending messages. It is invoked by +specifying the sender and recipients of a message and by passing the message +itself to the script's standard input. For example: + +{{{ +tools/sendmail.py paul.boddie@example.com resource-room-confroom@example.com \ + < tests/templates/event-request.txt +}}} + +Here, the sender (`paul.boddie@example.com`) and recipient +(`resource-room-confroom@example.com`) must match the identities specified +within the supplied file (`tests/templates/event-request.txt`), and for a +specific deployment environment these identities will need to be changed. +The following lines from the supplied file would be involved: + +{{{ +From: paul.boddie@example.com +To: resource-room-confroom@example.com +ORGANIZER:mailto:paul.boddie@example.com +ATTENDEE;ROLE=CHAIR:mailto:paul.boddie@example.com +ATTENDEE;RSVP=TRUE:mailto:resource-room-confroom@example.com +}}} + +It makes most sense to choose a recipient acting as a resource so that an +automated response may be generated with the sender receiving this response. +However, other kinds of recipients may also be tested in this way. + +The result of this invocation will become known via the following sources of +information: + + * The sender's mailbox (if the recipient sends an automated response) + * The sender's data store + * The recipient's mailbox (if the recipient is configured to store mail) + * The recipient's data store + * Mail system logs (particularly in case of errors) + +See the [[../MailIntegration|mail integration guide]] for more information +about configuring and troubleshooting mail systems. diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/__init__.py --- a/imiptools/__init__.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/__init__.py Fri Apr 22 16:22:58 2016 +0200 @@ -25,7 +25,7 @@ from imiptools.content import handle_itip_part from imiptools.data import get_address, get_addresses, get_uri from imiptools.mail import Messenger -import imiptools.stores.file +from imiptools.stores import get_store, get_publisher, get_journal import sys, os # Postfix exit codes. @@ -56,6 +56,7 @@ self.outgoing_only = outgoing_only self.messenger = None self.lmtp_socket = None + self.store_type = None self.store_dir = None self.publishing_dir = None self.journal_dir = None @@ -63,13 +64,13 @@ self.debug = False def get_store(self): - return imiptools.stores.file.FileStore(self.store_dir) + return get_store(self.store_type, self.store_dir) def get_publisher(self): - return self.publishing_dir and imiptools.stores.file.FilePublisher(self.publishing_dir) or None + return self.publishing_dir and get_publisher(self.publishing_dir) or None def get_journal(self): - return imiptools.stores.file.FileJournal(self.journal_dir) + return get_journal(self.store_type, self.journal_dir) def process(self, f, original_recipients): @@ -122,6 +123,7 @@ recipients = [] senders = [] lmtp = [] + store_type = [] store_dir = [] publishing_dir = [] preferences_dir = [] @@ -152,6 +154,11 @@ elif arg == "-L": local_smtp = True + # Switch to getting the store type. + + elif arg == "-T": + l = store_type + # Switch to getting the store directory. elif arg == "-S": @@ -179,11 +186,14 @@ else: l.append(arg) - self.messenger = Messenger(lmtp_socket=lmtp and lmtp[0] or None, local_smtp=local_smtp, sender=senders and senders[0] or None) - self.store_dir = store_dir and store_dir[0] or None - self.publishing_dir = publishing_dir and publishing_dir[0] or None - self.preferences_dir = preferences_dir and preferences_dir[0] or None - self.journal_dir = journal_dir and journal_dir[0] or None + getvalue = lambda value, default=None: value and value[0] or default + + self.messenger = Messenger(lmtp_socket=getvalue(lmtp), local_smtp=local_smtp, sender=getvalue(senders)) + self.store_type = getvalue(store_type, config.STORE_TYPE) + self.store_dir = getvalue(store_dir) + self.publishing_dir = getvalue(publishing_dir) + self.preferences_dir = getvalue(preferences_dir) + self.journal_dir = getvalue(journal_dir) self.process(stream, original_recipients) def __call__(self): @@ -198,6 +208,7 @@ if "--help" in args: print >>sys.stderr, """\ Usage: %s [ -o ... ] [-s ... ] [ -l | -L ] \\ + [ -T ] [ -P ] \\ [ -p ] [ -j ] [ -d ] @@ -225,6 +236,7 @@ -p Indicates the location of user preference directories -S Indicates the location of the calendar data store containing user storage directories +-T Indicates the store and journal type (the configured value if omitted) Output options: diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/client.py --- a/imiptools/client.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/client.py Fri Apr 22 16:22:58 2016 +0200 @@ -27,11 +27,8 @@ from imiptools.dates import check_permitted_values, format_datetime, get_default_timezone, \ get_duration, get_timestamp from imiptools.i18n import get_translator -from imiptools.period import can_schedule, remove_event_periods, \ - remove_additional_periods, remove_affected_period, \ - update_freebusy from imiptools.profile import Preferences -import imiptools.stores.file +from imiptools.stores import get_store, get_publisher, get_journal class Client: @@ -51,11 +48,11 @@ self.user = user self.messenger = messenger - self.store = store or imiptools.stores.file.FileStore() - self.journal = journal or imiptools.stores.file.FileJournal() + self.store = store or get_store(config.STORE_TYPE, config.STORE_DIR) + self.journal = journal or get_journal(config.STORE_TYPE, config.JOURNAL_DIR) try: - self.publisher = publisher or imiptools.stores.file.FilePublisher() + self.publisher = publisher or get_publisher(config.PUBLISH_DIR) except OSError: self.publisher = None @@ -288,7 +285,7 @@ offer. """ - update_freebusy(freebusy, periods, transp, uid, recurrenceid, summary, organiser, expires) + freebusy.update_freebusy(periods, transp, uid, recurrenceid, summary, organiser, expires) # Preparation of messages communicating the state of events. @@ -388,6 +385,13 @@ return get_uri(self.obj.get_value("ORGANIZER")) == self.user + def is_recurrence(self): + + "Return whether the current object is a recurrence of its parent." + + parent = self.get_parent_object() + return parent and parent.has_recurrence(self.get_tzid(), self.obj.get_recurrenceid()) + # Common operations on calendar data. def update_senders(self, obj=None): @@ -626,7 +630,7 @@ # this point, so we update our copy for serialisation as the bundled # free/busy object. - freebusy = self.store.get_freebusy(self.user) + freebusy = self.store.get_freebusy(self.user).copy() self.update_freebusy(freebusy, self.user, from_organiser) # Bundle free/busy information if appropriate. @@ -862,7 +866,7 @@ Indicate whether within 'freebusy' the given 'periods' can be scheduled. """ - return can_schedule(freebusy, periods, self.uid, self.recurrenceid) + return freebusy.can_schedule(periods, self.uid, self.recurrenceid) def have_new_object(self, strict=True): @@ -993,9 +997,9 @@ "Remove this event from the given 'freebusy' collection." - removed = remove_event_periods(freebusy, self.uid, self.recurrenceid) + removed = freebusy.remove_event_periods(self.uid, self.recurrenceid) if not removed and self.recurrenceid: - return remove_affected_period(freebusy, self.uid, self.get_recurrence_start_point(self.recurrenceid)) + return freebusy.remove_affected_period(self.uid, self.get_recurrence_start_point(self.recurrenceid)) else: return removed @@ -1011,18 +1015,18 @@ if self.recurrenceid: recurrenceid = self.get_recurrence_start_point(self.recurrenceid) - remove_affected_period(freebusy, self.uid, recurrenceid) + freebusy.remove_affected_period(self.uid, recurrenceid) else: # Remove obsolete recurrence periods. - remove_additional_periods(freebusy, self.uid, recurrenceids) + freebusy.remove_additional_periods(self.uid, recurrenceids) # Remove original periods affected by additional recurrences. if recurrenceids: for recurrenceid in recurrenceids: recurrenceid = self.get_recurrence_start_point(recurrenceid) - remove_affected_period(freebusy, self.uid, recurrenceid) + freebusy.remove_affected_period(self.uid, recurrenceid) def update_freebusy(self, freebusy, user, as_organiser, offer=False): @@ -1137,7 +1141,7 @@ self.acquire_lock() try: - freebusy = self.store.get_freebusy_for_other(self.user, user) + freebusy = self.store.get_freebusy_for_other_for_update(self.user, user) fn(freebusy, user, for_organiser, True) # Tidy up any obsolete recurrences. @@ -1192,7 +1196,7 @@ organiser of an event if 'for_organiser' is set to a true value. """ - freebusy = self.store.get_freebusy(self.user) + freebusy = self.store.get_freebusy_for_update(self.user) # Obtain the attendance attributes for this user, if available. @@ -1219,7 +1223,7 @@ "Remove free/busy information when handling an object." - freebusy = self.store.get_freebusy(self.user) + freebusy = self.store.get_freebusy_for_update(self.user) self.remove_from_freebusy(freebusy) self.remove_freebusy_for_recurrences(freebusy) @@ -1238,7 +1242,7 @@ "Update free/busy offers when handling an object." - freebusy = self.store.get_freebusy_offers(self.user) + freebusy = self.store.get_freebusy_offers_for_update(self.user) # Obtain the attendance attributes for this user, if available. @@ -1256,7 +1260,7 @@ "Remove free/busy offers when handling an object." - freebusy = self.store.get_freebusy_offers(self.user) + freebusy = self.store.get_freebusy_offers_for_update(self.user) self.remove_from_freebusy(freebusy) self.remove_freebusy_for_recurrences(freebusy) diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/config.py --- a/imiptools/config.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/config.py Fri Apr 22 16:22:58 2016 +0200 @@ -14,6 +14,10 @@ OUTGOING_PREFIX = "people-outgoing" +# The store (and journal) type. + +STORE_TYPE = "file" + # The location of the stored calendar information. STORE_DIR = "/var/lib/imip-agent/store" diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/data.py --- a/imiptools/data.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/data.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ Interpretation of vCalendar content. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -29,7 +29,7 @@ get_recurrence_start_point, \ get_time, get_tzid, to_datetime, to_timezone, \ to_utc_datetime -from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod, period_overlaps +from imiptools.period import FreeBusyPeriod, Period, RecurringPeriod from vCalendar import iterwrite, parse, ParseError, to_dict, to_node from vRecurrence import get_parameters, get_rule import email.utils @@ -44,6 +44,25 @@ "Access to calendar structures." def __init__(self, fragment): + + """ + Initialise the object with the given 'fragment'. This must be a + dictionary mapping an object type (such as "VEVENT") to a tuple + containing the object details and attributes, each being a dictionary + itself. + + The result of parse_object can be processed to obtain a fragment by + obtaining a collection of records for an object type. For example: + + l = parse_object(f, encoding, "VCALENDAR") + events = l["VEVENT"] + event = events[0] + + Then, the specific object must be presented as follows: + + object = Object({"VEVENT" : event}) + """ + self.objtype, (self.details, self.attr) = fragment.items()[0] def get_uid(self): @@ -148,6 +167,9 @@ def to_part(self, method): return to_part(method, [self.to_node()]) + def to_string(self): + return to_string(self.to_node()) + # Direct access to the structure. def has_key(self, name): @@ -216,7 +238,7 @@ return (dtstart, dtstart_attr), (dtend, dtend_attr) - def get_periods(self, tzid, end=None): + def get_periods(self, tzid, end=None, inclusive=False): """ Return periods defined by this object, employing the given 'tzid' where @@ -225,9 +247,34 @@ If 'end' is omitted, only explicit recurrences and recurrences from explicitly-terminated rules will be returned. + + If 'inclusive' is set to a true value, any period occurring at the 'end' + will be included. + """ + + return get_periods(self, tzid, end, inclusive) + + def has_period(self, tzid, period): + + """ + Return whether this object, employing the given 'tzid' where no time + zone information is defined, has the given 'period'. """ - return get_periods(self, tzid, end) + return period in self.get_periods(tzid, period.get_start_point(), inclusive=True) + + def has_recurrence(self, tzid, recurrenceid): + + """ + Return whether this object, employing the given 'tzid' where no time + zone information is defined, has the given 'recurrenceid'. + """ + + start_point = self.get_recurrence_start_point(recurrenceid, tzid) + for p in self.get_periods(tzid, start_point, inclusive=True): + if p.get_start_point() == start_point: + return True + return False def get_active_periods(self, recurrenceids, tzid, end=None): @@ -568,7 +615,7 @@ # Get a constrained view if start and end limits are specified. if period: - periods = period_overlaps(freebusy, period, True) + periods = freebusy.period_overlaps(period, True) else: periods = freebusy @@ -617,6 +664,19 @@ return None +def parse_string(s, encoding, objtype=None): + + """ + Parse the iTIP content from 's' having the given 'encoding'. If 'objtype' is + given, only objects of that type will be returned. Otherwise, the root of + the content will be returned as a dictionary with a single key indicating + the object type. + + Return None if the content was not readable or suitable. + """ + + return parse_object(StringIO(s), encoding, objtype) + def to_part(method, calendar): """ @@ -636,8 +696,23 @@ out.close() def to_stream(out, fragment, encoding="utf-8"): + + "Write to the 'out' stream the given 'fragment'." + iterwrite(out, encoding=encoding).append(fragment) +def to_string(fragment, encoding="utf-8"): + + "Return a string encoding the given 'fragment'." + + out = StringIO() + try: + to_stream(out, fragment, encoding) + return out.getvalue() + + finally: + out.close() + # Structure access functions. def get_items(d, name, all=True): diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/handlers/common.py --- a/imiptools/handlers/common.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/handlers/common.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ Common handler functionality for different entities. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -22,7 +22,7 @@ from imiptools.data import get_address, get_uri, make_freebusy, to_part, \ uri_dict from imiptools.dates import format_datetime -from imiptools.period import FreeBusyPeriod, Period, replace_overlapping +from imiptools.period import FreeBusyPeriod, Period class CommonFreebusy: @@ -58,8 +58,8 @@ period = Period(dtstart, dtend, self.get_tzid()) for sender, sender_attr in senders: - stored_freebusy = self.store.get_freebusy_for_other(self.user, sender) - replace_overlapping(stored_freebusy, period, freebusy) + stored_freebusy = self.store.get_freebusy_for_other_for_update(self.user, sender) + stored_freebusy.replace_overlapping(period, freebusy) self.store.set_freebusy_for_other(self.user, stored_freebusy, sender) def request(self): @@ -143,4 +143,50 @@ self.add_result("REFRESH", [get_address(organiser)], obj.to_part("REFRESH")) + def is_newly_separated_occurrence(self): + + "Return whether the current object is a newly-separated occurrence." + + # Obtain any stored object. + + obj = self.get_stored_object_version() + + # Handle any newly-separated, valid occurrence. + + return not obj and self.is_recurrence() + + def make_separate_occurrence(self, for_organiser=False): + + """ + Set the current object as a separate occurrence and redefine free/busy + records in terms of this new occurrence for other participants. + """ + + parent = self.get_parent_object() + if not parent: + return False + + # Transfer attendance information from the parent. + + parent_attendees = uri_dict(parent.get_value_map("ATTENDEE")) + attendee_map = uri_dict(self.obj.get_value_map("ATTENDEE")) + + for attendee, attendee_attr in parent_attendees.items(): + if not attendee_map.has_key(attendee): + attendee_map[attendee] = attendee_attr + + self.obj["ATTENDEE"] = attendee_map.items() + self.obj.remove_all(["RDATE", "RRULE"]) + + # Create or revive the occurrence. + + self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) + self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) + + # Update free/busy details for the current object for all attendees. + + self.update_freebusy_from_attendees(attendee_map.keys()) + + return True + # vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/handlers/person.py --- a/imiptools/handlers/person.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/handlers/person.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ Handlers for a person for whom scheduling is performed. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -205,10 +205,35 @@ "As organiser, update attendance from valid attendees." - if self.merge_attendance(attendees): - self.update_freebusy_from_attendees(attendees) + # Occurrences that are still part of a parent object are separated, + # attendance information transferred, and the free/busy details updated. + + if self.is_newly_separated_occurrence(): + if self.make_separate_occurrence(for_organiser=True): + + # Update free/busy details for the event. + + self.update_event_in_freebusy(for_organiser=True) + + # Produce a REQUEST for the created occurrence for other + # attendees of the parent event. - return True + obj = self.get_parent_object() + stored_attendees = set(obj.get_values("ATTENDEE")) + attendees = stored_attendees.difference(attendees) + + for attendee in attendees: + methods, parts = self.get_message_parts(self.obj, "REQUEST", attendee) + self.add_results(methods, [get_address(attendee)], parts) + + return True + + # Merge the attendance for the received object. + + elif self.merge_attendance(attendees): + return self.update_freebusy_from_attendees(attendees) + + return False def _refresh(self, organiser, attendees): diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/handlers/person_outgoing.py --- a/imiptools/handlers/person_outgoing.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/handlers/person_outgoing.py Fri Apr 22 16:22:58 2016 +0200 @@ -4,7 +4,7 @@ Handlers for a person for whom scheduling is performed, inspecting outgoing messages to obtain scheduling done externally. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -120,11 +120,19 @@ self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) else: + # Occurrences that are still part of a parent object are separated, + # attendance information transferred, and the free/busy details + # updated. + + if self.is_newly_separated_occurrence(): + self.make_separate_occurrence(for_organiser=not from_organiser) + # Obtain valid attendees, merging their attendance with the stored # object. - attendees = self.require_attendees(from_organiser) - self.merge_attendance(attendees) + else: + attendees = self.require_attendees(from_organiser) + self.merge_attendance(attendees) # Remove any associated request. diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/handlers/scheduling/freebusy.py --- a/imiptools/handlers/scheduling/freebusy.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/handlers/scheduling/freebusy.py Fri Apr 22 16:22:58 2016 +0200 @@ -21,8 +21,6 @@ from imiptools.data import uri_values from imiptools.dates import ValidityError, to_timezone -from imiptools.period import coalesce_freebusy, invert_freebusy, \ - periods_from, remove_periods def schedule_in_freebusy(handler, args, freebusy=None): @@ -105,29 +103,34 @@ # There should already be free/busy information for the user. user_freebusy = handler.get_store().get_freebusy(handler.user) - busy = user_freebusy + + # Maintain a separate copy of the data. + + busy = user_freebusy.copy() # Subtract any periods from this event from the free/busy collections. - event_periods = handler.remove_from_freebusy(user_freebusy) + event_periods = handler.remove_from_freebusy(busy) # Find busy periods for the other attendees. for attendee in uri_values(handler.obj.get_values("ATTENDEE")): if attendee != handler.user: - freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee) + + # Get a copy of the attendee's free/busy data. + + freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee).copy() if freebusy: - remove_periods(freebusy, event_periods) + freebusy.remove_periods(event_periods) busy += freebusy # Obtain the combined busy periods. - busy.sort() - busy = coalesce_freebusy(busy) + busy = busy.coalesce_freebusy() # Obtain free periods. - free = invert_freebusy(busy) + free = busy.invert_freebusy() permitted_values = handler.get_permitted_values() periods = [] @@ -153,7 +156,7 @@ # Get free periods from the time of each period. - for found in periods_from(free, period): + for found in free.periods_from(period): # Skip any periods before the last period. diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/handlers/scheduling/quota.py --- a/imiptools/handlers/scheduling/quota.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/handlers/scheduling/quota.py Fri Apr 22 16:22:58 2016 +0200 @@ -82,7 +82,7 @@ # Update the journal entries. journal = handler.get_journal() - entries = journal.get_entries(quota, group) + entries = journal.get_entries_for_update(quota, group) handler.update_freebusy(entries, group, False) journal.set_entries(quota, group, entries) @@ -105,7 +105,7 @@ # Update the journal entries. journal = handler.get_journal() - entries = journal.get_entries(quota, group) + entries = journal.get_entries_for_update(quota, group) handler.remove_from_freebusy(entries) journal.set_entries(quota, group, entries) @@ -209,7 +209,7 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - freebusy = journal.get_freebusy(quota, organiser) + freebusy = journal.get_freebusy_for_update(quota, organiser) handler.update_freebusy(freebusy, organiser, True) journal.set_freebusy(quota, organiser, freebusy) @@ -223,7 +223,7 @@ quota, organiser = _get_quota_and_identity(handler, args) journal = handler.get_journal() - freebusy = journal.get_freebusy(quota, organiser) + freebusy = journal.get_freebusy_for_update(quota, organiser) handler.remove_from_freebusy(freebusy) journal.set_freebusy(quota, organiser, freebusy) diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/period.py --- a/imiptools/period.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/period.py Fri Apr 22 16:22:58 2016 +0200 @@ -28,11 +28,27 @@ get_start_of_day, \ get_tzid, \ to_timezone, to_utc_datetime +from imiptools.sql import DatabaseOperations def ifnone(x, y): if x is None: return y else: return x +def from_strings(t, encoding): + return tuple([from_string(s, encoding) for s in t]) + +def from_string(s, encoding): + if s: + return unicode(s, encoding) + else: + return s + +def to_string(s, encoding): + if s: + return s.encode(encoding) + else: + return s + class Comparable: "A date/datetime wrapper that allows comparisons with other types." @@ -279,8 +295,13 @@ return None d = get_recurrence_start(recurrenceid) dt = get_recurrence_start_point(recurrenceid, self.tzid) - if self.get_start() == d or self.get_start_point() == dt: + + # Compare the start to dates only, using the normalised start datetime + # for comparisons with the start point. + + if not isinstance(d, datetime) and self.get_start() == d or self.get_start_point() == dt: return recurrenceid + return None # Value correction methods. @@ -356,17 +377,19 @@ self.organiser = organiser or None self.expires = expires or None - def as_tuple(self, strings_only=False): + def as_tuple(self, strings_only=False, string_datetimes=False): """ - Return the initialisation parameter tuple, converting false value - parameters to strings if 'strings_only' is set to a true value. + Return the initialisation parameter tuple, converting datetimes and + false value parameters to strings if 'strings_only' is set to a true + value. Otherwise, if 'string_datetimes' is set to a true value, only the + datetime values are converted to strings. """ null = lambda x: (strings_only and [""] or [x])[0] return ( - strings_only and format_datetime(self.get_start_point()) or self.start, - strings_only and format_datetime(self.get_end_point()) or self.end, + (strings_only or string_datetimes) and format_datetime(self.get_start_point()) or self.start, + (strings_only or string_datetimes) and format_datetime(self.get_end_point()) or self.end, self.uid or null(self.uid), self.transp or strings_only and "OPAQUE" or None, self.recurrenceid or null(self.recurrenceid), @@ -454,280 +477,675 @@ def make_corrected(self, start, end): return self.__class__(start, end, self.tzid, self.origin, self.get_start_attr(), self.get_end_attr()) -# Time and period management. +class FreeBusyCollectionBase: + + "Common operations on free/busy period collections." + + def __init__(self, mutable=True): + self.mutable = mutable + + def _check_mutable(self): + if not self.mutable: + raise TypeError, "Cannot mutate this collection." + + def copy(self): + + "Make an independent mutable copy of the collection." -def can_schedule(freebusy, periods, uid, recurrenceid): + return FreeBusyCollection(list(self), True) + + # List emulation methods. + + def __iadd__(self, periods): + for period in periods: + self.insert_period(period) + return self + + def append(self, period): + self.insert_period(period) + + # Operations. + + def can_schedule(self, periods, uid, recurrenceid): - """ - Return whether the 'freebusy' list can accommodate the given 'periods' - employing the specified 'uid' and 'recurrenceid'. - """ + """ + Return whether the collection can accommodate the given 'periods' + employing the specified 'uid' and 'recurrenceid'. + """ + + for conflict in self.have_conflict(periods, True): + if conflict.uid != uid or conflict.recurrenceid != recurrenceid: + return False + + return True + + def have_conflict(self, periods, get_conflicts=False): - for conflict in have_conflict(freebusy, periods, True): - if conflict.uid != uid or conflict.recurrenceid != recurrenceid: + """ + Return whether any period in the collection overlaps with the given + 'periods', returning a collection of such overlapping periods if + 'get_conflicts' is set to a true value. + """ + + conflicts = set() + for p in periods: + overlapping = self.period_overlaps(p, get_conflicts) + if overlapping: + if get_conflicts: + conflicts.update(overlapping) + else: + return True + + if get_conflicts: + return conflicts + else: return False - return True + def period_overlaps(self, period, get_periods=False): + + """ + Return whether any period in the collection overlaps with the given + 'period', returning a collection of overlapping periods if 'get_periods' + is set to a true value. + """ + + overlapping = self.get_overlapping(period) + + if get_periods: + return overlapping + else: + return len(overlapping) != 0 + + def replace_overlapping(self, period, replacements): + + """ + Replace existing periods in the collection within the given 'period', + using the given 'replacements'. + """ + + self._check_mutable() + + self.remove_overlapping(period) + for replacement in replacements: + self.insert_period(replacement) + + def coalesce_freebusy(self): + + "Coalesce the periods in the collection, returning a new collection." + + if not self: + return FreeBusyCollection() + + fb = [] + + it = iter(self) + period = it.next() + + start = period.get_start_point() + end = period.get_end_point() + + try: + while True: + period = it.next() + if period.get_start_point() > end: + fb.append(FreeBusyPeriod(start, end)) + start = period.get_start_point() + end = period.get_end_point() + else: + end = max(end, period.get_end_point()) + except StopIteration: + pass + + fb.append(FreeBusyPeriod(start, end)) + return FreeBusyCollection(fb) + + def invert_freebusy(self): + + "Return the free periods from the collection as a new collection." + + if not self: + return FreeBusyCollection([FreeBusyPeriod(None, None)]) + + # Coalesce periods that overlap or are adjacent. + + fb = self.coalesce_freebusy() + free = [] + + # Add a start-of-time period if appropriate. + + first = fb[0].get_start_point() + if first: + free.append(FreeBusyPeriod(None, first)) + + start = fb[0].get_end_point() + + for period in fb[1:]: + free.append(FreeBusyPeriod(start, period.get_start_point())) + start = period.get_end_point() + + # Add an end-of-time period if appropriate. + + if start: + free.append(FreeBusyPeriod(start, None)) + + return FreeBusyCollection(free) + + def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, expires=None): + + """ + Update the free/busy details with the given 'periods', 'transp' setting, + 'uid' plus 'recurrenceid' and 'summary' and 'organiser' details. + + An optional 'expires' datetime string indicates the expiry time of any + free/busy offer. + """ + + self._check_mutable() + + self.remove_event_periods(uid, recurrenceid) + + for p in periods: + self.insert_period(FreeBusyPeriod(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser, expires)) + +class FreeBusyCollection(FreeBusyCollectionBase): + + "An abstraction for a collection of free/busy periods." + + def __init__(self, periods=None, mutable=True): + + """ + Initialise the collection with the given list of 'periods', or start an + empty collection if no list is given. + """ + + FreeBusyCollectionBase.__init__(self, mutable) + self.periods = periods or [] + + # List emulation methods. + + def __nonzero__(self): + return bool(self.periods) + + def __iter__(self): + return iter(self.periods) + + def __len__(self): + return len(self.periods) + + def __getitem__(self, i): + return self.periods[i] + + # Operations. + + def insert_period(self, period): + + "Insert the given 'period' into the collection." + + self._check_mutable() + + i = bisect_left(self.periods, period) + if i == len(self.periods): + self.periods.append(period) + elif self.periods[i] != period: + self.periods.insert(i, period) + + def remove_periods(self, periods): + + "Remove the given 'periods' from the collection." + + self._check_mutable() -def have_conflict(freebusy, periods, get_conflicts=False): + for period in periods: + i = bisect_left(self.periods, period) + if i < len(self.periods) and self.periods[i] == period: + del self.periods[i] + + def remove_event_periods(self, uid, recurrenceid=None): + + """ + Remove from the collection all periods associated with 'uid' and + 'recurrenceid' (which if omitted causes the "parent" object's periods to + be referenced). + + Return the removed periods. + """ + + self._check_mutable() + + removed = [] + i = 0 + while i < len(self.periods): + fb = self.periods[i] + if fb.uid == uid and fb.recurrenceid == recurrenceid: + removed.append(self.periods[i]) + del self.periods[i] + else: + i += 1 + + return removed + + def remove_additional_periods(self, uid, recurrenceids=None): + + """ + Remove from the collection all periods associated with 'uid' having a + recurrence identifier indicating an additional or modified period. + + If 'recurrenceids' is specified, remove all periods associated with + 'uid' that do not have a recurrence identifier in the given list. + + Return the removed periods. + """ + + self._check_mutable() + + removed = [] + i = 0 + while i < len(self.periods): + fb = self.periods[i] + if fb.uid == uid and fb.recurrenceid and ( + recurrenceids is None or + recurrenceids is not None and fb.recurrenceid not in recurrenceids + ): + removed.append(self.periods[i]) + del self.periods[i] + else: + i += 1 + + return removed + + def remove_affected_period(self, uid, start): + + """ + Remove from the collection the period associated with 'uid' that + provides an occurrence starting at the given 'start' (provided by a + recurrence identifier, converted to a datetime). A recurrence identifier + is used to provide an alternative time period whilst also acting as a + reference to the originally-defined occurrence. + + Return any removed period in a list. + """ + + self._check_mutable() + + removed = [] + + search = Period(start, start) + found = bisect_left(self.periods, search) + + while found < len(self.periods): + fb = self.periods[found] + + # Stop looking if the start no longer matches the recurrence identifier. + + if fb.get_start_point() != search.get_start_point(): + break + + # If the period belongs to the parent object, remove it and return. + + if not fb.recurrenceid and uid == fb.uid: + removed.append(self.periods[found]) + del self.periods[found] + break + + # Otherwise, keep looking for a matching period. + + found += 1 + + return removed + + def periods_from(self, period): + + "Return the entries in the collection at or after 'period'." + + first = bisect_left(self.periods, period) + return self.periods[first:] + + def periods_until(self, period): + + "Return the entries in the collection before 'period'." + + last = bisect_right(self.periods, Period(period.get_end(), period.get_end(), period.get_tzid())) + return self.periods[:last] + + def get_overlapping(self, period): + + """ + Return the entries in the collection providing periods overlapping with + 'period'. + """ + + # Find the range of periods potentially overlapping the period in the + # free/busy collection. + + startpoints = self.periods_until(period) + + # Find the range of periods potentially overlapping the period in a version + # of the free/busy collection sorted according to end datetimes. + + endpoints = [(Period(fb.get_end_point(), fb.get_end_point()), fb) for fb in startpoints] + endpoints.sort() + first = bisect_left(endpoints, (Period(period.get_start_point(), period.get_start_point()),)) + endpoints = endpoints[first:] + + overlapping = set() + + for p, fb in endpoints: + if fb.overlaps(period): + overlapping.add(fb) + + overlapping = list(overlapping) + overlapping.sort() + return overlapping + + def remove_overlapping(self, period): + + "Remove all periods overlapping with 'period' from the collection." + + self._check_mutable() + + overlapping = self.get_overlapping(period) + + if overlapping: + for fb in overlapping: + self.periods.remove(fb) + +class FreeBusyDatabaseCollection(FreeBusyCollectionBase, DatabaseOperations): """ - Return whether any period in 'freebusy' overlaps with the given 'periods', - returning a collection of such overlapping periods if 'get_conflicts' is - set to a true value. - """ - - conflicts = set() - for p in periods: - overlapping = period_overlaps(freebusy, p, get_conflicts) - if overlapping: - if get_conflicts: - conflicts.update(overlapping) - else: - return True - - if get_conflicts: - return conflicts - else: - return False - -def insert_period(freebusy, period): - - "Insert into 'freebusy' the given 'period'." - - i = bisect_left(freebusy, period) - if i == len(freebusy): - freebusy.append(period) - elif freebusy[i] != period: - freebusy.insert(i, period) - -def remove_periods(freebusy, periods): - - "Remove from 'freebusy' the given 'periods'." - - for period in periods: - i = bisect_left(freebusy, period) - if i < len(freebusy) and freebusy[i] == period: - del freebusy[i] - -def remove_event_periods(freebusy, uid, recurrenceid=None): - - """ - Remove from 'freebusy' all periods associated with 'uid' and 'recurrenceid' - (which if omitted causes the "parent" object's periods to be referenced). - - Return the removed periods. - """ - - removed = [] - i = 0 - while i < len(freebusy): - fb = freebusy[i] - if fb.uid == uid and fb.recurrenceid == recurrenceid: - removed.append(freebusy[i]) - del freebusy[i] - else: - i += 1 - - return removed - -def remove_additional_periods(freebusy, uid, recurrenceids=None): - - """ - Remove from 'freebusy' all periods associated with 'uid' having a - recurrence identifier indicating an additional or modified period. - - If 'recurrenceids' is specified, remove all periods associated with 'uid' - that do not have a recurrence identifier in the given list. - - Return the removed periods. - """ - - removed = [] - i = 0 - while i < len(freebusy): - fb = freebusy[i] - if fb.uid == uid and fb.recurrenceid and ( - recurrenceids is None or - recurrenceids is not None and fb.recurrenceid not in recurrenceids - ): - removed.append(freebusy[i]) - del freebusy[i] - else: - i += 1 - - return removed - -def remove_affected_period(freebusy, uid, start): - - """ - Remove from 'freebusy' a period associated with 'uid' that provides an - occurrence starting at the given 'start' (provided by a recurrence - identifier, converted to a datetime). A recurrence identifier is used to - provide an alternative time period whilst also acting as a reference to the - originally-defined occurrence. - - Return any removed period in a list. + An abstraction for a collection of free/busy periods stored in a database + system. """ - removed = [] + period_columns = ["start", "end", "object_uid", "transp", "object_recurrenceid", "summary", "organiser", "expires"] + + def __init__(self, cursor, table_name, column_names=None, filter_values=None, mutable=True, paramstyle=None): - search = Period(start, start) - found = bisect_left(freebusy, search) + """ + Initialise the collection with the given 'cursor' and with the + 'table_name', 'column_names' and 'filter_values' configuring the + selection of data. + """ + + FreeBusyCollectionBase.__init__(self, mutable) + DatabaseOperations.__init__(self, column_names, filter_values, paramstyle) + self.cursor = cursor + self.table_name = table_name - while found < len(freebusy): - fb = freebusy[found] + def make_period(self, t): + return FreeBusyPeriod(*from_strings(t, "utf-8")) - # Stop looking if the start no longer matches the recurrence identifier. + # List emulation methods. + + def __nonzero__(self): + return len(self) and True or False - if fb.get_start_point() != search.get_start_point(): - break - - # If the period belongs to the parent object, remove it and return. + def __iter__(self): + query, values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }) + self.cursor.execute(query, values) + return iter(map(lambda t: self.make_period(t), self.cursor.fetchall())) - if not fb.recurrenceid and uid == fb.uid: - removed.append(freebusy[found]) - del freebusy[found] - break + def __len__(self): + query, values = self.get_query( + "select count(*) from %(table)s :condition" % { + "table" : self.table_name + }) + self.cursor.execute(query, values) + result = self.cursor.fetchone() + return result and int(result[0]) or 0 - # Otherwise, keep looking for a matching period. + def __getitem__(self, i): + return list(iter(self))[i] - found += 1 + # Operations. - return removed + def insert_period(self, period): + + "Insert the given 'period' into the collection." -def periods_from(freebusy, period): + self._check_mutable() + + columns, values = self.period_columns, period.as_tuple(string_datetimes=True) - "Return the entries in 'freebusy' at or after 'period'." + query, values = self.get_query( + "insert into %(table)s (:columns) values (:values)" % { + "table" : self.table_name + }, + columns, [to_string(v, "utf-8") for v in values]) - first = bisect_left(freebusy, period) - return freebusy[first:] + self.cursor.execute(query, values) + + def remove_periods(self, periods): -def periods_until(freebusy, period): + "Remove the given 'periods' from the collection." - "Return the entries in 'freebusy' before 'period'." + self._check_mutable() + + for period in periods: + values = period.as_tuple(string_datetimes=True) - last = bisect_right(freebusy, Period(period.get_end(), period.get_end(), period.get_tzid())) - return freebusy[:last] + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : self.table_name + }, + self.period_columns, [to_string(v, "utf-8") for v in values]) -def get_overlapping(freebusy, period): + self.cursor.execute(query, values) + + def remove_event_periods(self, uid, recurrenceid=None): + + """ + Remove from the collection all periods associated with 'uid' and + 'recurrenceid' (which if omitted causes the "parent" object's periods to + be referenced). - """ - Return the entries in 'freebusy' providing periods overlapping with - 'period'. - """ + Return the removed periods. + """ + + self._check_mutable() + + if recurrenceid: + columns, values = ["object_uid", "object_recurrenceid"], [uid, recurrenceid] + else: + columns, values = ["object_uid", "object_recurrenceid is null"], [uid] - # Find the range of periods potentially overlapping the period in the - # free/busy collection. + query, _values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) - startpoints = periods_until(freebusy, period) - - # Find the range of periods potentially overlapping the period in a version - # of the free/busy collection sorted according to end datetimes. + self.cursor.execute(query, _values) + removed = self.cursor.fetchall() - endpoints = [(Period(fb.get_end_point(), fb.get_end_point()), fb) for fb in startpoints] - endpoints.sort() - first = bisect_left(endpoints, (Period(period.get_start_point(), period.get_start_point()),)) - endpoints = endpoints[first:] + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : self.table_name + }, + columns, values) + + self.cursor.execute(query, values) - overlapping = set() + return map(lambda t: self.make_period(t), removed) + + def remove_additional_periods(self, uid, recurrenceids=None): - for p, fb in endpoints: - if fb.overlaps(period): - overlapping.add(fb) + """ + Remove from the collection all periods associated with 'uid' having a + recurrence identifier indicating an additional or modified period. + + If 'recurrenceids' is specified, remove all periods associated with + 'uid' that do not have a recurrence identifier in the given list. - overlapping = list(overlapping) - overlapping.sort() - return overlapping + Return the removed periods. + """ + + self._check_mutable() -def period_overlaps(freebusy, period, get_periods=False): + if not recurrenceids: + columns, values = ["object_uid", "object_recurrenceid is not null"], [uid] + else: + columns, values = ["object_uid", "object_recurrenceid not in ?", "object_recurrenceid is not null"], [uid, tuple(recurrenceids)] - """ - Return whether any period in 'freebusy' overlaps with the given 'period', - returning a collection of overlapping periods if 'get_periods' is set to a - true value. - """ + query, _values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) + + self.cursor.execute(query, _values) + removed = self.cursor.fetchall() - overlapping = get_overlapping(freebusy, period) + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : self.table_name + }, + columns, values) - if get_periods: - return overlapping - else: - return len(overlapping) != 0 + self.cursor.execute(query, values) -def remove_overlapping(freebusy, period): + return map(lambda t: self.make_period(t), removed) + + def remove_affected_period(self, uid, start): - "Remove from 'freebusy' all periods overlapping with 'period'." + """ + Remove from the collection the period associated with 'uid' that + provides an occurrence starting at the given 'start' (provided by a + recurrence identifier, converted to a datetime). A recurrence identifier + is used to provide an alternative time period whilst also acting as a + reference to the originally-defined occurrence. - overlapping = get_overlapping(freebusy, period) + Return any removed period in a list. + """ - if overlapping: - for fb in overlapping: - freebusy.remove(fb) + self._check_mutable() + + start = format_datetime(start) + + columns, values = ["object_uid", "start", "object_recurrenceid is null"], [uid, start] -def replace_overlapping(freebusy, period, replacements): + query, _values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) - """ - Replace existing periods in 'freebusy' within the given 'period', using the - given 'replacements'. - """ + self.cursor.execute(query, _values) + removed = self.cursor.fetchall() + + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : self.table_name + }, + columns, values) - remove_overlapping(freebusy, period) - for replacement in replacements: - insert_period(freebusy, replacement) + self.cursor.execute(query, values) + + return map(lambda t: self.make_period(t), removed) + + def periods_from(self, period): + + "Return the entries in the collection at or after 'period'." + + start = format_datetime(period.get_start_point()) -def coalesce_freebusy(freebusy): + columns, values = [], [] - "Coalesce the periods in 'freebusy'." + if start: + columns.append("start >= ?") + values.append(start) - if not freebusy: - return freebusy + query, values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) - fb = [] - start = freebusy[0].get_start_point() - end = freebusy[0].get_end_point() + self.cursor.execute(query, values) + + return map(lambda t: self.make_period(t), self.cursor.fetchall()) + + def periods_until(self, period): - for period in freebusy[1:]: - if period.get_start_point() > end: - fb.append(FreeBusyPeriod(start, end)) - start = period.get_start_point() - end = period.get_end_point() - else: - end = max(end, period.get_end_point()) + "Return the entries in the collection before 'period'." + + end = format_datetime(period.get_end_point()) + + columns, values = [], [] + + if end: + columns.append("start < ?") + values.append(end) - fb.append(FreeBusyPeriod(start, end)) - return fb + query, values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) -def invert_freebusy(freebusy): + self.cursor.execute(query, values) - "Return the free periods from 'freebusy'." + return map(lambda t: self.make_period(t), self.cursor.fetchall()) + + def get_overlapping(self, period): - if not freebusy: - return [FreeBusyPeriod(None, None)] + """ + Return the entries in the collection providing periods overlapping with + 'period'. + """ - # Coalesce periods that overlap or are adjacent. + columns, values = self._get_period_values(period) - fb = coalesce_freebusy(freebusy) - free = [] + query, values = self.get_query( + "select %(columns)s from %(table)s :condition" % { + "columns" : self.columnlist(self.period_columns), + "table" : self.table_name + }, + columns, values) - # Add a start-of-time period if appropriate. + self.cursor.execute(query, values) - first = fb[0].get_start_point() - if first: - free.append(FreeBusyPeriod(None, first)) + return map(lambda t: self.make_period(t), self.cursor.fetchall()) + + def remove_overlapping(self, period): - start = fb[0].get_end_point() + "Remove all periods overlapping with 'period' from the collection." + + self._check_mutable() + + columns, values = self._get_period_values(period) - for period in fb[1:]: - free.append(FreeBusyPeriod(start, period.get_start_point())) - start = period.get_end_point() + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : self.table_name + }, + columns, values) + + self.cursor.execute(query, values) + + def _get_period_values(self, period): - # Add an end-of-time period if appropriate. + start = format_datetime(period.get_start_point()) + end = format_datetime(period.get_end_point()) + + columns, values = [], [] - if start: - free.append(FreeBusyPeriod(start, None)) + if end: + columns.append("start < ?") + values.append(end) + if start: + columns.append("end > ?") + values.append(start) - return free + return columns, values # Period layout. @@ -1014,19 +1432,4 @@ return spans -def update_freebusy(freebusy, periods, transp, uid, recurrenceid, summary, organiser, expires=None): - - """ - Update the free/busy details with the given 'periods', 'transp' setting, - 'uid' plus 'recurrenceid' and 'summary' and 'organiser' details. - - An optional 'expires' datetime string indicates the expiry time of any - free/busy offer. - """ - - remove_event_periods(freebusy, uid, recurrenceid) - - for p in periods: - insert_period(freebusy, FreeBusyPeriod(p.get_start_point(), p.get_end_point(), uid, transp, recurrenceid, summary, organiser, expires)) - # vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/sql.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/sql.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,154 @@ +#!/usr/bin/env python + +""" +Database utilities. + +Copyright (C) 2016 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 . +""" + +import re + +class DatabaseOperations: + + "Special database-related operations." + + def __init__(self, column_names=None, filter_values=None, paramstyle=None): + self.column_names = column_names + self.filter_values = filter_values + self.paramstyle = paramstyle + + def get_query(self, query, columns=None, values=None, setcolumns=None, + setvalues=None): + + """ + Return 'query' parameterised with condition clauses indicated by + ":condition" in 'query' that are themselves populated using the given + 'columns' and 'values' together with any conditions provided when + initialising this class. + + If 'setcolumns' and 'setvalues' are given, such column details and + values will be used to parameterise ":set" clauses in the query. + """ + + columns = self.merge_default_columns(columns) + values = self.merge_default_values(values) + + condition = self.get_condition(columns) + columnlist = self.columnlist(columns) + placeholders = self.placeholders(values) + setters = self.get_setters(setcolumns) + + setvalues = setvalues or [] + + # Obtain the placeholder markers in order. + + parts = re.split("(:(?:condition|set|columns|values)(?=[^a-zA-Z]|$))", query) + + l = [parts[0]] + is_placeholder = True + all_values = [] + + for part in parts[1:]: + if is_placeholder: + + # Replace ":condition", replicating the given values. + + if part == ":condition": + all_values += values + l.append(condition) + + # Replace ":set", replicating the given values. + + elif part == ":set": + all_values += setvalues + l.append(setters) + + # Replace ":columns", providing a column list. + + elif part == ":columns": + l.append(columnlist) + + # Replace ":values", replicating the given values. + + elif part == ":values": + all_values += values + l.append(placeholders) + + else: + l.append(part) + else: + l.append(part) + + is_placeholder = not is_placeholder + + query = "".join(l) + return query, all_values + + def get_condition(self, columns=None): + + "Return a condition clause featuring the given 'columns'." + + l = self._get_columns(columns) + return "where %s" % " and ".join(l) + + def get_setters(self, columns=None): + + "Return set operations featuring the given 'columns'." + + l = self._get_columns(columns) + return "set %s" % ", ".join(l) + + def _get_columns(self, columns=None): + + "Return a list of statements or tests involving 'columns'." + + l = [] + + if columns: + for column in columns: + if " " in column: + column_name, remaining = column.split(" ", 1) + l.append("%s %s" % (self._quote(column_name), remaining.replace("?", self._param()))) + else: + l.append("%s = %s" % (self._quote(column), self._param())) + + return l + + def _quote(self, column): + return '"%s"' % column + + def merge_default_columns(self, columns=None): + return list(self.column_names or []) + list(columns or []) + + def merge_default_values(self, values=None): + return list(self.filter_values or []) + list(values or []) + + def columnlist(self, columns=None): + return ", ".join([self._quote(column) for column in columns]) + + def placeholders(self, values=None): + return ", ".join([self._param()] * len(values)) + + def _param(self): + + # NOTE: To be expanded. + + if self.paramstyle == "pyformat": + return "%s" + else: + return "?" + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/__init__.py --- a/imiptools/stores/__init__.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/stores/__init__.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ General support for calendar data storage. -Copyright (C) 2014, 2015, 2016 Paul Boddie +Copyright (C) 2016 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 @@ -19,568 +19,25 @@ this program. If not, see . """ -from imiptools.dates import format_datetime - -class StoreBase: - - "The core operations of a data store." - - def acquire_lock(self, user, timeout=None): - pass - - def release_lock(self, user): - pass - - # User discovery. - - def get_users(self): - - "Return a list of users." - - pass - - # Event and event metadata access. - - def get_events(self, user): - - "Return a list of event identifiers." - - pass - - def get_all_events(self, user): - - "Return a set of (uid, recurrenceid) tuples for all events." - - uids = self.get_events(user) - if not uids: - return set() - - all_events = set() - for uid in uids: - all_events.add((uid, None)) - all_events.update([(uid, recurrenceid) for recurrenceid in self.get_recurrences(user, uid)]) - - return all_events - - def get_event(self, user, uid, recurrenceid=None, dirname=None): - - """ - Get the event for the given 'user' with the given 'uid'. If - the optional 'recurrenceid' is specified, a specific instance or - occurrence of an event is returned. - """ - - pass - - def get_complete_event(self, user, uid): - - "Get the event for the given 'user' with the given 'uid'." - - pass - - def set_event(self, user, uid, recurrenceid, node): - - """ - Set an event for 'user' having the given 'uid' and 'recurrenceid' (which - if the latter is specified, a specific instance or occurrence of an - event is referenced), using the given 'node' description. - """ +from imiptools.stores import file +from imiptools.stores.database import stores as database_stores - if recurrenceid: - return self.set_recurrence(user, uid, recurrenceid, node) - else: - return self.set_complete_event(user, uid, node) - - def set_complete_event(self, user, uid, node): - - "Set an event for 'user' having the given 'uid' and 'node'." - - pass - - def remove_event(self, user, uid, recurrenceid=None): - - """ - Remove an event for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, a specific instance or occurrence of an - event is removed. - """ - - if recurrenceid: - return self.remove_recurrence(user, uid, recurrenceid) - else: - for recurrenceid in self.get_recurrences(user, uid) or []: - self.remove_recurrence(user, uid, recurrenceid) - return self.remove_complete_event(user, uid) - - def remove_complete_event(self, user, uid): - - "Remove an event for 'user' having the given 'uid'." - - self.remove_recurrences(user, uid) - return self.remove_parent_event(user, uid) - - def remove_parent_event(self, user, uid): - - "Remove the parent event for 'user' having the given 'uid'." - - pass - - def get_recurrences(self, user, uid): - - """ - Get additional event instances for an event of the given 'user' with the - indicated 'uid'. Both active and cancelled recurrences are returned. - """ - - return self.get_active_recurrences(user, uid) + self.get_cancelled_recurrences(user, uid) - - def get_active_recurrences(self, user, uid): - - """ - Get additional event instances for an event of the given 'user' with the - indicated 'uid'. Cancelled recurrences are not returned. - """ - - pass - - def get_cancelled_recurrences(self, user, uid): - - """ - Get additional event instances for an event of the given 'user' with the - indicated 'uid'. Only cancelled recurrences are returned. - """ - - pass - - def get_recurrence(self, user, uid, recurrenceid): - - """ - For the event of the given 'user' with the given 'uid', return the - specific recurrence indicated by the 'recurrenceid'. - """ +# Build a catalogue of store types. - pass - - def set_recurrence(self, user, uid, recurrenceid, node): - - "Set an event for 'user' having the given 'uid' and 'node'." - - pass - - def remove_recurrence(self, user, uid, recurrenceid): - - """ - Remove a special recurrence from an event stored by 'user' having the - given 'uid' and 'recurrenceid'. - """ - - pass - - def remove_recurrences(self, user, uid): - - """ - Remove all recurrences for an event stored by 'user' having the given - 'uid'. - """ - - for recurrenceid in self.get_recurrences(user, uid): - self.remove_recurrence(user, uid, recurrenceid) - - return self.remove_recurrence_collection(user, uid) - - def remove_recurrence_collection(self, user, uid): - - """ - Remove the collection of recurrences stored by 'user' having the given - 'uid'. - """ - - pass - - # Free/busy period providers, upon extension of the free/busy records. - - def _get_freebusy_providers(self, user): - - """ - Return the free/busy providers for the given 'user'. - - This function returns any stored datetime and a list of providers as a - 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. - """ - - pass - - def get_freebusy_providers(self, user, dt=None): - - """ - Return a set of uncancelled events of the form (uid, recurrenceid) - providing free/busy details beyond the given datetime 'dt'. - - If 'dt' is not specified, all events previously found to provide - details will be returned. Otherwise, if 'dt' is earlier than the - datetime recorded for the known providers, None is returned, indicating - that the list of providers must be recomputed. - - This function returns a list of (uid, recurrenceid) tuples upon success. - """ - - t = self._get_freebusy_providers(user) - if not t: - return None - - dt_string, t = t - - # If the requested datetime is earlier than the stated datetime, the - # providers will need to be recomputed. - - if dt: - providers_dt = get_datetime(dt_string) - if not providers_dt or providers_dt > dt: - return None - - # Otherwise, return the providers. - - return t[1:] - - def _set_freebusy_providers(self, user, dt_string, t): - - "Set the given provider timestamp 'dt_string' and table 't'." - - pass - - def set_freebusy_providers(self, user, dt, providers): - - """ - Define the uncancelled events providing free/busy details beyond the - given datetime 'dt'. - """ - - t = [] - - for obj in providers: - t.append((obj.get_uid(), obj.get_recurrenceid())) - - return self._set_freebusy_providers(user, format_datetime(dt), t) - - def append_freebusy_provider(self, user, provider): - - "For the given 'user', append the free/busy 'provider'." - - t = self._get_freebusy_providers(user) - if not t: - return False - - dt_string, t = t - t.append((provider.get_uid(), provider.get_recurrenceid())) - - return self._set_freebusy_providers(user, dt_string, t) - - def remove_freebusy_provider(self, user, provider): - - "For the given 'user', remove the free/busy 'provider'." - - t = self._get_freebusy_providers(user) - if not t: - return False - - dt_string, t = t - try: - t.remove((provider.get_uid(), provider.get_recurrenceid())) - except ValueError: - return False - - return self._set_freebusy_providers(user, dt_string, t) - - # Free/busy period access. - - def get_freebusy(self, user, name=None): - - "Get free/busy details for the given 'user'." - - pass - - def get_freebusy_for_other(self, user, other): +stores = { + "file" : file, + } +stores.update(database_stores) - "For the given 'user', get free/busy details for the 'other' user." - - pass - - def set_freebusy(self, user, freebusy, name=None): - - "For the given 'user', set 'freebusy' details." - - pass - - def set_freebusy_for_other(self, user, freebusy, other): - - "For the given 'user', set 'freebusy' details for the 'other' user." - - pass - - # Tentative free/busy periods related to countering. - - def get_freebusy_offers(self, user): - - "Get free/busy offers for the given 'user'." - - pass - - def set_freebusy_offers(self, user, freebusy): - - "For the given 'user', set 'freebusy' offers." - - return self.set_freebusy(user, freebusy, "freebusy-offers") - - # Requests and counter-proposals. - - def get_requests(self, user): - - "Get requests for the given 'user'." - - pass - - def set_requests(self, user, requests): - - "For the given 'user', set the list of queued 'requests'." - - pass - - def set_request(self, user, uid, recurrenceid=None, type=None): - - """ - For the given 'user', set the queued 'uid' and 'recurrenceid', - indicating a request, along with any given 'type'. - """ - - pass - - def queue_request(self, user, uid, recurrenceid=None, type=None): - - """ - Queue a request for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, the entry refers to a specific instance - or occurrence of an event. The 'type' parameter can be used to indicate - a specific type of request. - """ - - requests = self.get_requests(user) or [] - - if not self.have_request(requests, uid, recurrenceid): - return self.set_request(user, uid, recurrenceid, type) - - return False +# Access functions. - def dequeue_request(self, user, uid, recurrenceid=None): - - """ - Dequeue all requests for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, all requests for that specific instance or - occurrence of an event are dequeued. - """ - - requests = self.get_requests(user) or [] - result = [] - - for request in requests: - if request[:2] != (uid, recurrenceid): - result.append(request) - - self.set_requests(user, result) - return True - - def has_request(self, user, uid, recurrenceid=None, type=None, strict=False): - return self.have_request(self.get_requests(user) or [], uid, recurrenceid, type, strict) - - def have_request(self, requests, uid, recurrenceid=None, type=None, strict=False): - - """ - Return whether 'requests' contains a request with the given 'uid' and - any specified 'recurrenceid' and 'type'. If 'strict' is set to a true - value, the precise type of the request must match; otherwise, any type - of request for the identified object may be matched. - """ - - for request in requests: - if request[:2] == (uid, recurrenceid) and ( - not strict or - not request[2:] and not type or - request[2:] and request[2] == type): - - return True - - return False - - def get_counters(self, user, uid, recurrenceid=None): - - """ - For the given 'user', return a list of users from whom counter-proposals - have been received for the given 'uid' and optional 'recurrenceid'. - """ - - pass - - def get_counter(self, user, other, uid, recurrenceid=None): - - """ - For the given 'user', return the counter-proposal from 'other' for the - given 'uid' and optional 'recurrenceid'. - """ - - pass - - def set_counter(self, user, other, node, uid, recurrenceid=None): - - """ - For the given 'user', store a counter-proposal received from 'other' the - given 'node' representing that proposal for the given 'uid' and - 'recurrenceid'. - """ - - pass - - def remove_counters(self, user, uid, recurrenceid=None): +def get_store(store_type, store_dir): + return stores[store_type].Store(store_dir) - """ - For the given 'user', remove all counter-proposals associated with the - given 'uid' and 'recurrenceid'. - """ - - pass - - def remove_counter(self, user, other, uid, recurrenceid=None): - - """ - For the given 'user', remove any counter-proposal from 'other' - associated with the given 'uid' and 'recurrenceid'. - """ - - pass - - # Event cancellation. - - def cancel_event(self, user, uid, recurrenceid=None): - - """ - Cancel an event for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, a specific instance or occurrence of an - event is cancelled. - """ - - pass - - def uncancel_event(self, user, uid, recurrenceid=None): - - """ - Uncancel an event for 'user' having the given 'uid'. If the optional - 'recurrenceid' is specified, a specific instance or occurrence of an - event is uncancelled. - """ - - pass - - def remove_cancellations(self, user, uid, recurrenceid=None): - - """ - Remove cancellations for 'user' for any event having the given 'uid'. If - the optional 'recurrenceid' is specified, a specific instance or - occurrence of an event is affected. - """ - - # Remove all recurrence cancellations if a general event is indicated. - - if not recurrenceid: - for _recurrenceid in self.get_cancelled_recurrences(user, uid): - self.remove_cancellation(user, uid, _recurrenceid) - - return self.remove_cancellation(user, uid, recurrenceid) - - def remove_cancellation(self, user, uid, recurrenceid=None): - - """ - Remove a cancellation for 'user' for the event having the given 'uid'. - If the optional 'recurrenceid' is specified, a specific instance or - occurrence of an event is affected. - """ - - pass - -class PublisherBase: - - "The core operations of a data publisher." - - def set_freebusy(self, user, freebusy): +def get_publisher(publishing_dir): + return file.Publisher(publishing_dir) - "For the given 'user', set 'freebusy' details." - - pass - -class JournalBase: - - "The core operations of a journal system supporting quotas." - - # Quota and user identity/group discovery. - - def get_quotas(self): - - "Return a list of quotas." - - pass - - def get_quota_users(self, quota): - - "Return a list of quota users." - - pass - - # Groups of users sharing quotas. - - def get_groups(self, quota): - - "Return the identity mappings for the given 'quota' as a dictionary." - - pass - - def get_limits(self, quota): - - """ - Return the limits for the 'quota' as a dictionary mapping identities or - groups to durations. - """ - - pass - - # Free/busy period access for users within quota groups. - - def get_freebusy(self, quota, user): - - "Get free/busy details for the given 'quota' and 'user'." - - pass - - def set_freebusy(self, quota, user, freebusy): - - "For the given 'quota' and 'user', set 'freebusy' details." - - pass - - # Journal entry methods. - - def get_entries(self, quota, group): - - """ - Return a list of journal entries for the given 'quota' for the indicated - 'group'. - """ - - pass - - def set_entries(self, quota, group, entries): - - """ - For the given 'quota' and indicated 'group', set the list of journal - 'entries'. - """ - - pass +def get_journal(store_type, journal_dir): + return stores[store_type].Journal(journal_dir) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/stores/common.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,693 @@ +#!/usr/bin/env python + +""" +General support for calendar data storage. + +Copyright (C) 2014, 2015, 2016 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.dates import format_datetime, get_datetime + +class StoreBase: + + "The core operations of a data store." + + # User discovery. + + def get_users(self): + + "Return a list of users." + + pass + + # Event and event metadata access. + + def get_all_events(self, user, dirname=None): + + """ + Return a set of (uid, recurrenceid) tuples for all events. Unless + 'dirname' is specified, only active events are returned; otherwise, + events from the given 'dirname' are returned. + """ + + cancelled = self.get_cancelled_events(user) + active = self.get_events(user) + + if dirname == "cancellations": + uids = cancelled + else: + uids = active + + if not uids: + return set() + + all_events = set() + + # Add all qualifying parent events to the result set. + + for uid in uids: + all_events.add((uid, None)) + + # Process all parent events regardless of status to find those in the + # category/directory of interest. + + for uid in active + cancelled: + + if dirname == "cancellations": + recurrenceids = self.get_cancelled_recurrences(user, uid) + else: + recurrenceids = self.get_active_recurrences(user, uid) + + all_events.update([(uid, recurrenceid) for recurrenceid in recurrenceids]) + + return all_events + + def get_events(self, user): + + "Return a list of event identifiers." + + pass + + def get_cancelled_events(self, user): + + "Return a list of event identifiers for cancelled events." + + pass + + def get_event(self, user, uid, recurrenceid=None, dirname=None): + + """ + Get the event for the given 'user' with the given 'uid'. If + the optional 'recurrenceid' is specified, a specific instance or + occurrence of an event is returned. + """ + + pass + + def get_complete_event(self, user, uid): + + "Get the event for the given 'user' with the given 'uid'." + + pass + + def set_event(self, user, uid, recurrenceid, node): + + """ + Set an event for 'user' having the given 'uid' and 'recurrenceid' (which + if the latter is specified, a specific instance or occurrence of an + event is referenced), using the given 'node' description. + """ + + if recurrenceid: + return self.set_recurrence(user, uid, recurrenceid, node) + else: + return self.set_complete_event(user, uid, node) + + def set_complete_event(self, user, uid, node): + + "Set an event for 'user' having the given 'uid' and 'node'." + + pass + + def remove_event(self, user, uid, recurrenceid=None): + + """ + Remove an event for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, a specific instance or occurrence of an + event is removed. + """ + + if recurrenceid: + return self.remove_recurrence(user, uid, recurrenceid) + else: + for recurrenceid in self.get_recurrences(user, uid) or []: + self.remove_recurrence(user, uid, recurrenceid) + return self.remove_complete_event(user, uid) + + def remove_complete_event(self, user, uid): + + "Remove an event for 'user' having the given 'uid'." + + self.remove_recurrences(user, uid) + return self.remove_parent_event(user, uid) + + def remove_parent_event(self, user, uid): + + "Remove the parent event for 'user' having the given 'uid'." + + pass + + def get_recurrences(self, user, uid): + + """ + Get additional event instances for an event of the given 'user' with the + indicated 'uid'. Both active and cancelled recurrences are returned. + """ + + return self.get_active_recurrences(user, uid) + self.get_cancelled_recurrences(user, uid) + + def get_active_recurrences(self, user, uid): + + """ + Get additional event instances for an event of the given 'user' with the + indicated 'uid'. Cancelled recurrences are not returned. + """ + + pass + + def get_cancelled_recurrences(self, user, uid): + + """ + Get additional event instances for an event of the given 'user' with the + indicated 'uid'. Only cancelled recurrences are returned. + """ + + pass + + def get_recurrence(self, user, uid, recurrenceid): + + """ + For the event of the given 'user' with the given 'uid', return the + specific recurrence indicated by the 'recurrenceid'. + """ + + pass + + def set_recurrence(self, user, uid, recurrenceid, node): + + "Set an event for 'user' having the given 'uid' and 'node'." + + pass + + def remove_recurrence(self, user, uid, recurrenceid): + + """ + Remove a special recurrence from an event stored by 'user' having the + given 'uid' and 'recurrenceid'. + """ + + pass + + def remove_recurrences(self, user, uid): + + """ + Remove all recurrences for an event stored by 'user' having the given + 'uid'. + """ + + for recurrenceid in self.get_recurrences(user, uid): + self.remove_recurrence(user, uid, recurrenceid) + + return self.remove_recurrence_collection(user, uid) + + def remove_recurrence_collection(self, user, uid): + + """ + Remove the collection of recurrences stored by 'user' having the given + 'uid'. + """ + + pass + + # Free/busy period providers, upon extension of the free/busy records. + + def _get_freebusy_providers(self, user): + + """ + Return the free/busy providers for the given 'user'. + + This function returns any stored datetime and a list of providers as a + 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. + """ + + pass + + def get_freebusy_providers(self, user, dt=None): + + """ + Return a set of uncancelled events of the form (uid, recurrenceid) + providing free/busy details beyond the given datetime 'dt'. + + If 'dt' is not specified, all events previously found to provide + details will be returned. Otherwise, if 'dt' is earlier than the + datetime recorded for the known providers, None is returned, indicating + that the list of providers must be recomputed. + + This function returns a list of (uid, recurrenceid) tuples upon success. + """ + + t = self._get_freebusy_providers(user) + if not t: + return None + + dt_string, t = t + + # If the requested datetime is earlier than the stated datetime, the + # providers will need to be recomputed. + + if dt: + providers_dt = get_datetime(dt_string) + if not providers_dt or providers_dt > dt: + return None + + # Otherwise, return the providers. + + return t + + def _set_freebusy_providers(self, user, dt_string, t): + + "Set the given provider timestamp 'dt_string' and table 't'." + + pass + + def set_freebusy_providers(self, user, dt, providers): + + """ + Define the uncancelled events providing free/busy details beyond the + given datetime 'dt'. + """ + + t = [] + + for obj in providers: + t.append((obj.get_uid(), obj.get_recurrenceid())) + + return self._set_freebusy_providers(user, format_datetime(dt), t) + + def append_freebusy_provider(self, user, provider): + + "For the given 'user', append the free/busy 'provider'." + + t = self._get_freebusy_providers(user) + if not t: + return False + + dt_string, t = t + t.append((provider.get_uid(), provider.get_recurrenceid())) + + return self._set_freebusy_providers(user, dt_string, t) + + def remove_freebusy_provider(self, user, provider): + + "For the given 'user', remove the free/busy 'provider'." + + t = self._get_freebusy_providers(user) + if not t: + return False + + dt_string, t = t + try: + t.remove((provider.get_uid(), provider.get_recurrenceid())) + except ValueError: + return False + + return self._set_freebusy_providers(user, dt_string, t) + + # Free/busy period access. + + def get_freebusy(self, user, name=None, mutable=False): + + "Get free/busy details for the given 'user'." + + pass + + def get_freebusy_for_other(self, user, other, mutable=False): + + "For the given 'user', get free/busy details for the 'other' user." + + pass + + def get_freebusy_for_update(self, user, name=None): + + """ + Get free/busy details for the given 'user' that may be updated, + potentially affecting the stored information directly. + """ + + return self.get_freebusy(user, name, True) + + def get_freebusy_for_other_for_update(self, user, other): + + """ + For the given 'user', get free/busy details for the 'other' user + that may be updated, potentially affecting the stored information + directly. + """ + + return self.get_freebusy_for_other(user, other, True) + + def set_freebusy(self, user, freebusy, name=None): + + "For the given 'user', set 'freebusy' details." + + pass + + def set_freebusy_for_other(self, user, freebusy, other): + + "For the given 'user', set 'freebusy' details for the 'other' user." + + pass + + def get_freebusy_others(self, user): + + """ + For the given 'user', return a list of other users for whom free/busy + information is retained. + """ + + pass + + # Tentative free/busy periods related to countering. + + def get_freebusy_offers(self, user, mutable=False): + + "Get free/busy offers for the given 'user'." + + pass + + def get_freebusy_offers_for_update(self, user): + + """ + Get free/busy offers for the given 'user' that may be updated, + potentially affecting the stored information directly. + """ + + return self.get_freebusy_offers(user, True) + + def set_freebusy_offers(self, user, freebusy): + + "For the given 'user', set 'freebusy' offers." + + return self.set_freebusy(user, freebusy, "freebusy-offers") + + # Requests and counter-proposals. + + def get_requests(self, user): + + "Get requests for the given 'user'." + + pass + + def set_requests(self, user, requests): + + "For the given 'user', set the list of queued 'requests'." + + pass + + def set_request(self, user, uid, recurrenceid=None, type=None): + + """ + For the given 'user', set the queued 'uid' and 'recurrenceid', + indicating a request, along with any given 'type'. + """ + + pass + + def queue_request(self, user, uid, recurrenceid=None, type=None): + + """ + Queue a request for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, the entry refers to a specific instance + or occurrence of an event. The 'type' parameter can be used to indicate + a specific type of request. + """ + + requests = self.get_requests(user) or [] + + if not self.have_request(requests, uid, recurrenceid): + return self.set_request(user, uid, recurrenceid, type) + + return False + + def dequeue_request(self, user, uid, recurrenceid=None): + + """ + Dequeue all requests for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, all requests for that specific instance or + occurrence of an event are dequeued. + """ + + requests = self.get_requests(user) or [] + result = [] + + for request in requests: + if request[:2] != (uid, recurrenceid): + result.append(request) + + self.set_requests(user, result) + return True + + def has_request(self, user, uid, recurrenceid=None, type=None, strict=False): + return self.have_request(self.get_requests(user) or [], uid, recurrenceid, type, strict) + + def have_request(self, requests, uid, recurrenceid=None, type=None, strict=False): + + """ + Return whether 'requests' contains a request with the given 'uid' and + any specified 'recurrenceid' and 'type'. If 'strict' is set to a true + value, the precise type of the request must match; otherwise, any type + of request for the identified object may be matched. + """ + + for request in requests: + if request[:2] == (uid, recurrenceid) and ( + not strict or + not request[2:] and not type or + request[2:] and request[2] == type): + + return True + + return False + + def get_counters(self, user, uid, recurrenceid=None): + + """ + For the given 'user', return a list of users from whom counter-proposals + have been received for the given 'uid' and optional 'recurrenceid'. + """ + + pass + + def get_counter(self, user, other, uid, recurrenceid=None): + + """ + For the given 'user', return the counter-proposal from 'other' for the + given 'uid' and optional 'recurrenceid'. + """ + + pass + + def set_counter(self, user, other, node, uid, recurrenceid=None): + + """ + For the given 'user', store a counter-proposal received from 'other' the + given 'node' representing that proposal for the given 'uid' and + 'recurrenceid'. + """ + + pass + + def remove_counters(self, user, uid, recurrenceid=None): + + """ + For the given 'user', remove all counter-proposals associated with the + given 'uid' and 'recurrenceid'. + """ + + pass + + def remove_counter(self, user, other, uid, recurrenceid=None): + + """ + For the given 'user', remove any counter-proposal from 'other' + associated with the given 'uid' and 'recurrenceid'. + """ + + pass + + # Event cancellation. + + def cancel_event(self, user, uid, recurrenceid=None): + + """ + Cancel an event for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, a specific instance or occurrence of an + event is cancelled. + """ + + pass + + def uncancel_event(self, user, uid, recurrenceid=None): + + """ + Uncancel an event for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, a specific instance or occurrence of an + event is uncancelled. + """ + + pass + + def remove_cancellations(self, user, uid, recurrenceid=None): + + """ + Remove cancellations for 'user' for any event having the given 'uid'. If + the optional 'recurrenceid' is specified, a specific instance or + occurrence of an event is affected. + """ + + # Remove all recurrence cancellations if a general event is indicated. + + if not recurrenceid: + for _recurrenceid in self.get_cancelled_recurrences(user, uid): + self.remove_cancellation(user, uid, _recurrenceid) + + return self.remove_cancellation(user, uid, recurrenceid) + + def remove_cancellation(self, user, uid, recurrenceid=None): + + """ + Remove a cancellation for 'user' for the event having the given 'uid'. + If the optional 'recurrenceid' is specified, a specific instance or + occurrence of an event is affected. + """ + + pass + +class PublisherBase: + + "The core operations of a data publisher." + + def set_freebusy(self, user, freebusy): + + "For the given 'user', set 'freebusy' details." + + pass + +class JournalBase: + + "The core operations of a journal system supporting quotas." + + # Quota and user identity/group discovery. + + def get_quotas(self): + + "Return a list of quotas." + + pass + + def get_quota_users(self, quota): + + "Return a list of quota users." + + pass + + # Groups of users sharing quotas. + + def get_groups(self, quota): + + "Return the identity mappings for the given 'quota' as a dictionary." + + pass + + def set_group(self, quota, store_user, user_group): + + """ + For the given 'quota', set a mapping from 'store_user' to 'user_group'. + """ + + pass + + def get_limits(self, quota): + + """ + Return the limits for the 'quota' as a dictionary mapping identities or + groups to durations. + """ + + pass + + def set_limit(self, quota, group, limit): + + """ + For the given 'quota', set for a user 'group' the given 'limit' on + resource usage. + """ + + pass + + # Free/busy period access for users within quota groups. + + def get_freebusy_users(self, quota): + + """ + Return a list of users whose free/busy details are retained for the + given 'quota'. + """ + + pass + + def get_freebusy(self, quota, user, mutable=False): + + "Get free/busy details for the given 'quota' and 'user'." + + pass + + def get_freebusy_for_update(self, quota, user): + + """ + Get free/busy details for the given 'quota' and 'user' that may be + updated, potentially affecting the stored information directly. + """ + + return self.get_freebusy(quota, user, True) + + def set_freebusy(self, quota, user, freebusy): + + "For the given 'quota' and 'user', set 'freebusy' details." + + pass + + # Journal entry methods. + + def get_entries(self, quota, group, mutable=False): + + """ + Return a list of journal entries for the given 'quota' for the indicated + 'group'. + """ + + pass + + def get_entries_for_update(self, quota, group): + + """ + Return a list of journal entries for the given 'quota' for the indicated + 'group' that may be updated, potentially affecting the stored + information directly. + """ + + return self.get_entries(quota, group, True) + + def set_entries(self, quota, group, entries): + + """ + For the given 'quota' and indicated 'group', set the list of journal + 'entries'. + """ + + pass + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/database/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/stores/database/__init__.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,28 @@ +#!/usr/bin/env python + +""" +General support for database stores. + +Copyright (C) 2016 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.stores.database import postgresql + +stores = { + "postgresql" : postgresql, + } + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/database/common.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/stores/database/common.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,1001 @@ +#!/usr/bin/env python + +""" +A database store of calendar data. + +Copyright (C) 2014, 2015, 2016 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.stores.common import StoreBase, JournalBase + +from datetime import datetime +from imiptools.data import parse_string, to_string +from imiptools.dates import format_datetime, get_datetime, to_timezone +from imiptools.period import FreeBusyDatabaseCollection +from imiptools.sql import DatabaseOperations + +class DatabaseStoreBase(DatabaseOperations): + + "A database store supporting user-specific locking." + + def __init__(self, connection, paramstyle=None): + DatabaseOperations.__init__(self, paramstyle=paramstyle) + self.connection = connection + self.cursor = connection.cursor() + + def acquire_lock(self, user, timeout=None): + pass + + def release_lock(self, user): + pass + +class DatabaseStore(DatabaseStoreBase, StoreBase): + + "A database store of tabular free/busy data and objects." + + # User discovery. + + def get_users(self): + + "Return a list of users." + + query = "select distinct store_user from (" \ + "select store_user from freebusy " \ + "union all select store_user from objects " \ + "union all select store_user from recurrences" \ + ") as users" + self.cursor.execute(query) + return [r[0] for r in self.cursor.fetchall()] + + # Event and event metadata access. + + def get_all_events(self, user, dirname=None): + + """ + Return a set of (uid, recurrenceid) tuples for all events. Unless + 'dirname' is specified, only active events are returned; otherwise, + events from the given 'dirname' are returned. + """ + + columns, values = self.get_event_table_filters(dirname) + + columns += ["store_user"] + values += [user] + + query, values = self.get_query( + "select object_uid, null as object_recurrenceid from objects :condition " + "union all " + "select object_uid, object_recurrenceid from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + return self.cursor.fetchall() + + def get_events(self, user, dirname=None): + + "Return a list of event identifiers." + + columns, values = self.get_event_table_filters(dirname) + + columns += ["store_user"] + values += [user] + + query, values = self.get_query( + "select object_uid from objects :condition", + columns, values) + + self.cursor.execute(query, values) + return [r[0] for r in self.cursor.fetchall()] + + def get_cancelled_events(self, user): + + "Return a list of event identifiers for cancelled events." + + return self.get_events(user, "cancellations") + + def get_event(self, user, uid, recurrenceid=None, dirname=None): + + """ + Get the event for the given 'user' with the given 'uid'. If + the optional 'recurrenceid' is specified, a specific instance or + occurrence of an event is returned. + """ + + table = self.get_event_table(recurrenceid, dirname) + columns, values = self.get_event_table_filters(dirname) + + if recurrenceid: + columns += ["store_user", "object_uid", "object_recurrenceid"] + values += [user, uid, recurrenceid] + else: + columns += ["store_user", "object_uid"] + values += [user, uid] + + query, values = self.get_query( + "select object_text from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + result = self.cursor.fetchone() + return result and parse_string(result[0], "utf-8") + + def get_complete_event(self, user, uid): + + "Get the event for the given 'user' with the given 'uid'." + + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "select object_text from objects :condition", + columns, values) + + self.cursor.execute(query, values) + result = self.cursor.fetchone() + return result and parse_string(result[0], "utf-8") + + def set_complete_event(self, user, uid, node): + + "Set an event for 'user' having the given 'uid' and 'node'." + + columns = ["store_user", "object_uid"] + values = [user, uid] + setcolumns = ["object_text", "status"] + setvalues = [to_string(node, "utf-8"), "active"] + + query, values = self.get_query( + "update objects :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0 or self.get_complete_event(user, uid): + return True + + columns = ["store_user", "object_uid", "object_text", "status"] + values = [user, uid, to_string(node, "utf-8"), "active"] + + query, values = self.get_query( + "insert into objects (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + def remove_parent_event(self, user, uid): + + "Remove the parent event for 'user' having the given 'uid'." + + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "delete from objects :condition", + columns, values) + + self.cursor.execute(query, values) + return self.cursor.rowcount > 0 + + def get_active_recurrences(self, user, uid): + + """ + Get additional event instances for an event of the given 'user' with the + indicated 'uid'. Cancelled recurrences are not returned. + """ + + columns = ["store_user", "object_uid", "status"] + values = [user, uid, "active"] + + query, values = self.get_query( + "select object_recurrenceid from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + return [t[0] for t in self.cursor.fetchall() or []] + + def get_cancelled_recurrences(self, user, uid): + + """ + Get additional event instances for an event of the given 'user' with the + indicated 'uid'. Only cancelled recurrences are returned. + """ + + columns = ["store_user", "object_uid", "status"] + values = [user, uid, "cancelled"] + + query, values = self.get_query( + "select object_recurrenceid from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + return [t[0] for t in self.cursor.fetchall() or []] + + def get_recurrence(self, user, uid, recurrenceid): + + """ + For the event of the given 'user' with the given 'uid', return the + specific recurrence indicated by the 'recurrenceid'. + """ + + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + + query, values = self.get_query( + "select object_text from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + result = self.cursor.fetchone() + return result and parse_string(result[0], "utf-8") + + def set_recurrence(self, user, uid, recurrenceid, node): + + "Set an event for 'user' having the given 'uid' and 'node'." + + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + setcolumns = ["object_text", "status"] + setvalues = [to_string(node, "utf-8"), "active"] + + query, values = self.get_query( + "update recurrences :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0 or self.get_recurrence(user, uid, recurrenceid): + return True + + columns = ["store_user", "object_uid", "object_recurrenceid", "object_text", "status"] + values = [user, uid, recurrenceid, to_string(node, "utf-8"), "active"] + + query, values = self.get_query( + "insert into recurrences (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + def remove_recurrence(self, user, uid, recurrenceid): + + """ + Remove a special recurrence from an event stored by 'user' having the + given 'uid' and 'recurrenceid'. + """ + + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + + query, values = self.get_query( + "delete from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + return True + + def remove_recurrences(self, user, uid): + + """ + Remove all recurrences for an event stored by 'user' having the given + 'uid'. + """ + + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "delete from recurrences :condition", + columns, values) + + self.cursor.execute(query, values) + return True + + # Event table computation. + + def get_event_table(self, recurrenceid=None, dirname=None): + + "Get the table providing events for any specified 'dirname'." + + if recurrenceid: + return self.get_recurrence_table(dirname) + else: + return self.get_complete_event_table(dirname) + + def get_event_table_filters(self, dirname=None): + + "Get filter details for any specified 'dirname'." + + if dirname == "cancellations": + return ["status"], ["cancelled"] + else: + return ["status"], ["active"] + + def get_complete_event_table(self, dirname=None): + + "Get the table providing events for any specified 'dirname'." + + if dirname == "counters": + return "countered_objects" + else: + return "objects" + + def get_recurrence_table(self, dirname=None): + + "Get the table providing recurrences for any specified 'dirname'." + + if dirname == "counters": + return "countered_recurrences" + else: + return "recurrences" + + # Free/busy period providers, upon extension of the free/busy records. + + def _get_freebusy_providers(self, user): + + """ + Return the free/busy providers for the given 'user'. + + This function returns any stored datetime and a list of providers as a + 2-tuple. Each provider is itself a (uid, recurrenceid) tuple. + """ + + columns = ["store_user"] + values = [user] + + query, values = self.get_query( + "select object_uid, object_recurrenceid from freebusy_providers :condition", + columns, values) + + self.cursor.execute(query, values) + providers = self.cursor.fetchall() + + columns = ["store_user"] + values = [user] + + query, values = self.get_query( + "select start from freebusy_provider_datetimes :condition", + columns, values) + + self.cursor.execute(query, values) + result = self.cursor.fetchone() + dt_string = result and result[0] + + return dt_string, providers + + def _set_freebusy_providers(self, user, dt_string, t): + + "Set the given provider timestamp 'dt_string' and table 't'." + + # NOTE: Locking? + + columns = ["store_user"] + values = [user] + + query, values = self.get_query( + "delete from freebusy_providers :condition", + columns, values) + + self.cursor.execute(query, values) + + columns = ["store_user", "object_uid", "object_recurrenceid"] + + for uid, recurrenceid in t: + values = [user, uid, recurrenceid] + + query, values = self.get_query( + "insert into freebusy_providers (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + + columns = ["store_user"] + values = [user] + setcolumns = ["start"] + setvalues = [dt_string] + + query, values = self.get_query( + "update freebusy_provider_datetimes :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0: + return True + + columns = ["store_user", "start"] + values = [user, dt_string] + + query, values = self.get_query( + "insert into freebusy_provider_datetimes (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + # Free/busy period access. + + def get_freebusy(self, user, name=None, mutable=False): + + "Get free/busy details for the given 'user'." + + table = name or "freebusy" + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], mutable, self.paramstyle) + + def get_freebusy_for_other(self, user, other, mutable=False): + + "For the given 'user', get free/busy details for the 'other' user." + + table = "freebusy_other" + return FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], mutable, self.paramstyle) + + def set_freebusy(self, user, freebusy, name=None): + + "For the given 'user', set 'freebusy' details." + + table = name or "freebusy" + + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user"], [user], True, self.paramstyle) + fbc += freebusy + + return True + + def set_freebusy_for_other(self, user, freebusy, other): + + "For the given 'user', set 'freebusy' details for the 'other' user." + + table = "freebusy_other" + + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["store_user", "other"], [user, other], True, self.paramstyle) + fbc += freebusy + + return True + + def get_freebusy_others(self, user): + + """ + For the given 'user', return a list of other users for whom free/busy + information is retained. + """ + + columns = ["store_user"] + values = [user] + + query, values = self.get_query( + "select distinct other from freebusy_other :condition", + columns, values) + + self.cursor.execute(query, values) + return [r[0] for r in self.cursor.fetchall()] + + # Tentative free/busy periods related to countering. + + def get_freebusy_offers(self, user, mutable=False): + + "Get free/busy offers for the given 'user'." + + # Expire old offers and save the collection if modified. + + now = format_datetime(to_timezone(datetime.utcnow(), "UTC")) + columns = ["store_user", "expires"] + values = [user, now] + + query, values = self.get_query( + "delete from freebusy_offers :condition", + columns, values) + + self.cursor.execute(query, values) + + return self.get_freebusy(user, "freebusy_offers", mutable) + + def set_freebusy_offers(self, user, freebusy): + + "For the given 'user', set 'freebusy' offers." + + return self.set_freebusy(user, freebusy, "freebusy_offers") + + # Requests and counter-proposals. + + def get_requests(self, user): + + "Get requests for the given 'user'." + + columns = ["store_user"] + values = [user] + + query, values = self.get_query( + "select object_uid, object_recurrenceid, request_type from requests :condition", + columns, values) + + self.cursor.execute(query, values) + return self.cursor.fetchall() + + def set_request(self, user, uid, recurrenceid=None, type=None): + + """ + For the given 'user', set the queued 'uid' and 'recurrenceid', + indicating a request, along with any given 'type'. + """ + + columns = ["store_user", "object_uid", "object_recurrenceid", "request_type"] + values = [user, uid, recurrenceid, type] + + query, values = self.get_query( + "insert into requests (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + def queue_request(self, user, uid, recurrenceid=None, type=None): + + """ + Queue a request for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, the entry refers to a specific instance + or occurrence of an event. The 'type' parameter can be used to indicate + a specific type of request. + """ + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + setcolumns = ["request_type"] + setvalues = [type] + + query, values = self.get_query( + "update requests :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0: + return + + self.set_request(user, uid, recurrenceid, type) + + def dequeue_request(self, user, uid, recurrenceid=None): + + """ + Dequeue all requests for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, all requests for that specific instance or + occurrence of an event are dequeued. + """ + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "delete from requests :condition", + columns, values) + + self.cursor.execute(query, values) + return True + + def get_counters(self, user, uid, recurrenceid=None): + + """ + For the given 'user', return a list of users from whom counter-proposals + have been received for the given 'uid' and optional 'recurrenceid'. + """ + + table = self.get_event_table(recurrenceid, "counters") + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "select other from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + return [r[0] for r in self.cursor.fetchall()] + + def get_counter(self, user, other, uid, recurrenceid=None): + + """ + For the given 'user', return the counter-proposal from 'other' for the + given 'uid' and optional 'recurrenceid'. + """ + + table = self.get_event_table(recurrenceid, "counters") + + if recurrenceid: + columns = ["store_user", "other", "object_uid", "object_recurrenceid"] + values = [user, other, uid, recurrenceid] + else: + columns = ["store_user", "other", "object_uid"] + values = [user, other, uid] + + query, values = self.get_query( + "select object_text from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + result = self.cursor.fetchone() + return result and parse_string(result[0], "utf-8") + + def set_counter(self, user, other, node, uid, recurrenceid=None): + + """ + For the given 'user', store a counter-proposal received from 'other' the + given 'node' representing that proposal for the given 'uid' and + 'recurrenceid'. + """ + + table = self.get_event_table(recurrenceid, "counters") + + if recurrenceid: + columns = ["store_user", "other", "object_uid", "object_recurrenceid", "object_text"] + values = [user, other, uid, recurrenceid, to_string(node, "utf-8")] + else: + columns = ["store_user", "other", "object_uid", "object_text"] + values = [user, other, uid, to_string(node, "utf-8")] + + query, values = self.get_query( + "insert into %(table)s (:columns) values (:values)" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + return True + + def remove_counters(self, user, uid, recurrenceid=None): + + """ + For the given 'user', remove all counter-proposals associated with the + given 'uid' and 'recurrenceid'. + """ + + table = self.get_event_table(recurrenceid, "counters") + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + return True + + def remove_counter(self, user, other, uid, recurrenceid=None): + + """ + For the given 'user', remove any counter-proposal from 'other' + associated with the given 'uid' and 'recurrenceid'. + """ + + table = self.get_event_table(recurrenceid, "counters") + + if recurrenceid: + columns = ["store_user", "other", "object_uid", "object_recurrenceid"] + values = [user, other, uid, recurrenceid] + else: + columns = ["store_user", "other", "object_uid"] + values = [user, other, uid] + + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + return True + + # Event cancellation. + + def cancel_event(self, user, uid, recurrenceid=None): + + """ + Cancel an event for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, a specific instance or occurrence of an + event is cancelled. + """ + + table = self.get_event_table(recurrenceid) + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + setcolumns = ["status"] + setvalues = ["cancelled"] + + query, values = self.get_query( + "update %(table)s :set :condition" % { + "table" : table + }, + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + return True + + def uncancel_event(self, user, uid, recurrenceid=None): + + """ + Uncancel an event for 'user' having the given 'uid'. If the optional + 'recurrenceid' is specified, a specific instance or occurrence of an + event is uncancelled. + """ + + table = self.get_event_table(recurrenceid) + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid"] + values = [user, uid, recurrenceid] + else: + columns = ["store_user", "object_uid"] + values = [user, uid] + + setcolumns = ["status"] + setvalues = ["active"] + + query, values = self.get_query( + "update %(table)s :set :condition" % { + "table" : table + }, + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + return True + + def remove_cancellation(self, user, uid, recurrenceid=None): + + """ + Remove a cancellation for 'user' for the event having the given 'uid'. + If the optional 'recurrenceid' is specified, a specific instance or + occurrence of an event is affected. + """ + + table = self.get_event_table(recurrenceid) + + if recurrenceid: + columns = ["store_user", "object_uid", "object_recurrenceid", "status"] + values = [user, uid, recurrenceid, "cancelled"] + else: + columns = ["store_user", "object_uid", "status"] + values = [user, uid, "cancelled"] + + query, values = self.get_query( + "delete from %(table)s :condition" % { + "table" : table + }, + columns, values) + + self.cursor.execute(query, values) + return True + +class DatabaseJournal(DatabaseStoreBase, JournalBase): + + "A journal system to support quotas." + + # Quota and user identity/group discovery. + + def get_quotas(self): + + "Return a list of quotas." + + query = "select distinct quota from (" \ + "select quota from quota_freebusy " \ + "union all select quota from quota_limits" \ + ") as quotas" + self.cursor.execute(query) + return [r[0] for r in self.cursor.fetchall()] + + def get_quota_users(self, quota): + + "Return a list of quota users." + + columns = ["quota"] + values = [quota] + + query, values = self.get_query( + "select distinct user_group from quota_freebusy :condition", + columns, values) + + self.cursor.execute(query, values) + return [r[0] for r in self.cursor.fetchall()] + + # Groups of users sharing quotas. + + def get_groups(self, quota): + + "Return the identity mappings for the given 'quota' as a dictionary." + + columns = ["quota"] + values = [quota] + + query, values = self.get_query( + "select store_user, user_group from user_groups :condition", + columns, values) + + self.cursor.execute(query, values) + return dict(self.cursor.fetchall()) + + def set_group(self, quota, store_user, user_group): + + """ + For the given 'quota', set a mapping from 'store_user' to 'user_group'. + """ + + columns = ["quota", "store_user"] + values = [quota, store_user] + setcolumns = ["user_group"] + setvalues = [user_group] + + query, values = self.get_query( + "update user_groups :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0: + return True + + columns = ["quota", "store_user", "user_group"] + values = [quota, store_user, user_group] + + query, values = self.get_query( + "insert into user_groups (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + def get_limits(self, quota): + + """ + Return the limits for the 'quota' as a dictionary mapping identities or + groups to durations. + """ + + columns = ["quota"] + values = [quota] + + query, values = self.get_query( + "select user_group, quota_limit from quota_limits :condition", + columns, values) + + self.cursor.execute(query, values) + return dict(self.cursor.fetchall()) + + def set_limit(self, quota, group, limit): + + """ + For the given 'quota', set for a user 'group' the given 'limit' on + resource usage. + """ + + columns = ["quota", "user_group"] + values = [quota, group] + setcolumns = ["quota_limit"] + setvalues = [limit] + + query, values = self.get_query( + "update quota_limits :set :condition", + columns, values, setcolumns, setvalues) + + self.cursor.execute(query, values) + + if self.cursor.rowcount > 0: + return True + + columns = ["quota", "user_group", "quota_limit"] + values = [quota, group, limit] + + query, values = self.get_query( + "insert into quota_limits (:columns) values (:values)", + columns, values) + + self.cursor.execute(query, values) + return True + + # Free/busy period access for users within quota groups. + + def get_freebusy_users(self, quota): + + """ + Return a list of users whose free/busy details are retained for the + given 'quota'. + """ + + columns = ["quota"] + values = [quota] + + query, values = self.get_query( + "select distinct store_user from user_freebusy :condition", + columns, values) + + self.cursor.execute(query, values) + return [r[0] for r in self.cursor.fetchall()] + + def get_freebusy(self, quota, user, mutable=False): + + "Get free/busy details for the given 'quota' and 'user'." + + table = "user_freebusy" + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], mutable, self.paramstyle) + + def set_freebusy(self, quota, user, freebusy): + + "For the given 'quota' and 'user', set 'freebusy' details." + + table = "user_freebusy" + + if not isinstance(freebusy, FreeBusyDatabaseCollection) or freebusy.table_name != table: + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "store_user"], [quota, user], True, self.paramstyle) + fbc += freebusy + + return True + + # Journal entry methods. + + def get_entries(self, quota, group, mutable=False): + + """ + Return a list of journal entries for the given 'quota' for the indicated + 'group'. + """ + + table = "quota_freebusy" + return FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], mutable, self.paramstyle) + + def set_entries(self, quota, group, entries): + + """ + For the given 'quota' and indicated 'group', set the list of journal + 'entries'. + """ + + table = "quota_freebusy" + + if not isinstance(entries, FreeBusyDatabaseCollection) or entries.table_name != table: + fbc = FreeBusyDatabaseCollection(self.cursor, table, ["quota", "user_group"], [quota, group], True, self.paramstyle) + fbc += entries + + return True + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/database/postgresql.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/imiptools/stores/database/postgresql.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,66 @@ +#!/usr/bin/env python + +""" +A PostgreSQL database store of calendar data. + +Copyright (C) 2016 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 STORE_DIR, JOURNAL_DIR +from imiptools.stores.database.common import DatabaseStore, DatabaseJournal +import psycopg2 + +class Store(DatabaseStore): + + "A PostgreSQL database store of calendar objects and free/busy data." + + def __init__(self, store_dir=None): + + "Interpret 'store_dir' as a connection string." + + connection = psycopg2.connect(store_dir or STORE_DIR) + connection.autocommit = True + DatabaseStore.__init__(self, connection, psycopg2.paramstyle) + + def acquire_lock(self, user, timeout=None): + query = "select pg_advisory_lock(20160311)" + self.cursor.execute(query) + + def release_lock(self, user): + query = "select pg_advisory_unlock(20160311)" + self.cursor.execute(query) + +class Journal(DatabaseJournal): + + "A PostgreSQL journal system supporting quotas." + + def __init__(self, store_dir=None): + + "Interpret 'store_dir' as a connection string." + + connection = psycopg2.connect(store_dir or JOURNAL_DIR) + connection.autocommit = True + DatabaseJournal.__init__(self, connection, psycopg2.paramstyle) + + def acquire_lock(self, user, timeout=None): + query = "select pg_advisory_lock(20160312)" + self.cursor.execute(query) + + def release_lock(self, user): + query = "select pg_advisory_unlock(20160312)" + self.cursor.execute(query) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imiptools/stores/file.py --- a/imiptools/stores/file.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imiptools/stores/file.py Fri Apr 22 16:22:58 2016 +0200 @@ -19,14 +19,14 @@ this program. If not, see . """ -from imiptools.stores import StoreBase, PublisherBase, JournalBase +from imiptools.stores.common import StoreBase, PublisherBase, JournalBase from datetime import datetime from imiptools.config import STORE_DIR, PUBLISH_DIR, JOURNAL_DIR from imiptools.data import make_calendar, parse_object, to_stream from imiptools.dates import format_datetime, get_datetime, to_timezone from imiptools.filesys import fix_permissions, FileBase -from imiptools.period import FreeBusyPeriod +from imiptools.period import FreeBusyPeriod, FreeBusyCollection from imiptools.text import parse_line from os.path import isdir, isfile, join from os import listdir, remove, rmdir @@ -148,7 +148,7 @@ finally: self.release_lock(user) -class FileStore(FileStoreBase, StoreBase): +class Store(FileStoreBase, StoreBase): "A file store of tabular free/busy data and objects." @@ -232,25 +232,19 @@ filename = self.get_object_in_store(user, "objects") if not filename or not isdir(filename): - return None + return [] return [name for name in listdir(filename) if isfile(join(filename, name))] - def get_event_filename(self, user, uid, recurrenceid=None, dirname=None, username=None): + def get_cancelled_events(self, user): - """ - Get the filename providing the event for the given 'user' with the given - 'uid'. If the optional 'recurrenceid' is specified, a specific instance - or occurrence of an event is returned. + "Return a list of event identifiers for cancelled events." - Where 'dirname' is specified, the given directory name is used as the - base of the location within which any filename will reside. - """ + filename = self.get_object_in_store(user, "cancellations", "objects") + if not filename or not isdir(filename): + return [] - if recurrenceid: - return self.get_recurrence_filename(user, uid, recurrenceid, dirname, username) - else: - return self.get_complete_event_filename(user, uid, dirname, username) + return [name for name in listdir(filename) if isfile(join(filename, name))] def get_event(self, user, uid, recurrenceid=None, dirname=None): @@ -266,21 +260,6 @@ return filename and self._get_object(user, filename) - def get_complete_event_filename(self, user, uid, dirname=None, username=None): - - """ - Get the filename providing the event for the given 'user' with the given - 'uid'. - - Where 'dirname' is specified, the given directory name is used as the - base of the location within which any filename will reside. - - Where 'username' is specified, the event details will reside in a file - bearing that name within a directory having 'uid' as its name. - """ - - return self.get_object_in_store(user, dirname, "objects", uid, username) - def get_complete_event(self, user, uid): "Get the event for the given 'user' with the given 'uid'." @@ -346,21 +325,6 @@ return [name for name in listdir(filename) if isfile(join(filename, name))] - def get_recurrence_filename(self, user, uid, recurrenceid, dirname=None, username=None): - - """ - For the event of the given 'user' with the given 'uid', return the - filename providing the recurrence with the given 'recurrenceid'. - - Where 'dirname' is specified, the given directory name is used as the - base of the location within which any filename will reside. - - Where 'username' is specified, the event details will reside in a file - bearing that name within a directory having 'uid' as its name. - """ - - return self.get_object_in_store(user, dirname, "recurrences", uid, recurrenceid, username) - def get_recurrence(self, user, uid, recurrenceid): """ @@ -410,6 +374,54 @@ return True + # Event filename computation. + + def get_event_filename(self, user, uid, recurrenceid=None, dirname=None, username=None): + + """ + Get the filename providing the event for the given 'user' with the given + 'uid'. If the optional 'recurrenceid' is specified, a specific instance + or occurrence of an event is returned. + + Where 'dirname' is specified, the given directory name is used as the + base of the location within which any filename will reside. + """ + + if recurrenceid: + return self.get_recurrence_filename(user, uid, recurrenceid, dirname, username) + else: + return self.get_complete_event_filename(user, uid, dirname, username) + + def get_recurrence_filename(self, user, uid, recurrenceid, dirname=None, username=None): + + """ + For the event of the given 'user' with the given 'uid', return the + filename providing the recurrence with the given 'recurrenceid'. + + Where 'dirname' is specified, the given directory name is used as the + base of the location within which any filename will reside. + + Where 'username' is specified, the event details will reside in a file + bearing that name within a directory having 'uid' as its name. + """ + + return self.get_object_in_store(user, dirname, "recurrences", uid, recurrenceid, username) + + def get_complete_event_filename(self, user, uid, dirname=None, username=None): + + """ + Get the filename providing the event for the given 'user' with the given + 'uid'. + + Where 'dirname' is specified, the given directory name is used as the + base of the location within which any filename will reside. + + Where 'username' is specified, the event details will reside in a file + bearing that name within a directory having 'uid' as its name. + """ + + return self.get_object_in_store(user, dirname, "objects", uid, username) + # Free/busy period providers, upon extension of the free/busy records. def _get_freebusy_providers(self, user): @@ -450,28 +462,34 @@ # Free/busy period access. - def get_freebusy(self, user, name=None): + def get_freebusy(self, user, name=None, mutable=False): "Get free/busy details for the given 'user'." filename = self.get_object_in_store(user, name or "freebusy") + if not filename or not isfile(filename): - return [] + periods = [] else: - return map(lambda t: FreeBusyPeriod(*t), + periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(user, filename)) - def get_freebusy_for_other(self, user, other): + return FreeBusyCollection(periods, mutable) + + def get_freebusy_for_other(self, user, other, mutable=False): "For the given 'user', get free/busy details for the 'other' user." filename = self.get_object_in_store(user, "freebusy-other", other) + if not filename or not isfile(filename): - return [] + periods = [] else: - return map(lambda t: FreeBusyPeriod(*t), + periods = map(lambda t: FreeBusyPeriod(*t), self._get_table_atomic(user, filename)) + return FreeBusyCollection(periods, mutable) + def set_freebusy(self, user, freebusy, name=None): "For the given 'user', set 'freebusy' details." @@ -481,7 +499,7 @@ return False self._set_table_atomic(user, filename, - map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) + map(lambda fb: fb.as_tuple(strings_only=True), list(freebusy))) return True def set_freebusy_for_other(self, user, freebusy, other): @@ -493,12 +511,26 @@ return False self._set_table_atomic(user, filename, - map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) + map(lambda fb: fb.as_tuple(strings_only=True), list(freebusy))) return True + def get_freebusy_others(self, user): + + """ + For the given 'user', return a list of other users for whom free/busy + information is retained. + """ + + filename = self.get_object_in_store(user, "freebusy-other") + + if not filename or not isdir(filename): + return [] + + return listdir(filename) + # Tentative free/busy periods related to countering. - def get_freebusy_offers(self, user): + def get_freebusy_offers(self, user, mutable=False): "Get free/busy offers for the given 'user'." @@ -522,7 +554,7 @@ finally: self.release_lock(user) - return offers + return FreeBusyCollection(offers, mutable) # Requests and counter-proposals. @@ -532,7 +564,7 @@ filename = self.get_object_in_store(user, queue) if not filename or not isfile(filename): - return None + return [] return self._get_table_atomic(user, filename, [(1, None), (2, None)]) @@ -603,7 +635,7 @@ filename = self.get_event_filename(user, uid, recurrenceid, "counters") if not filename or not isdir(filename): - return False + return [] return [name for name in listdir(filename) if isfile(join(filename, name))] @@ -615,8 +647,8 @@ """ filename = self.get_event_filename(user, uid, recurrenceid, "counters", other) - if not filename: - return False + if not filename or not isfile(filename): + return None return self._get_object(user, filename) @@ -718,7 +750,7 @@ return False -class FilePublisher(FileBase, PublisherBase): +class Publisher(FileBase, PublisherBase): "A publisher of objects." @@ -754,7 +786,7 @@ return True -class FileJournal(FileStoreBase, JournalBase): +class Journal(FileStoreBase, JournalBase): "A journal system to support quotas." @@ -791,6 +823,22 @@ return dict(self._get_table_atomic(quota, filename, tab_separated=False)) + def set_group(self, quota, store_user, user_group): + + """ + For the given 'quota', set a mapping from 'store_user' to 'user_group'. + """ + + filename = self.get_object_in_store(quota, "groups") + if not filename: + return False + + groups = self.get_groups(quota) or {} + groups[store_user] = user_group + + self._set_table_atomic(quota, filename, groups.items()) + return True + def get_limits(self, quota): """ @@ -800,22 +848,55 @@ filename = self.get_object_in_store(quota, "limits") if not filename or not isfile(filename): - return None + return {} return dict(self._get_table_atomic(quota, filename, tab_separated=False)) + def set_limit(self, quota, group, limit): + + """ + For the given 'quota', set for a user 'group' the given 'limit' on + resource usage. + """ + + filename = self.get_object_in_store(quota, "limits") + if not filename: + return False + + limits = self.get_limits(quota) or {} + limits[group] = limit + + self._set_table_atomic(quota, filename, limits.items()) + return True + # Free/busy period access for users within quota groups. - def get_freebusy(self, quota, user): + def get_freebusy_users(self, quota): + + """ + Return a list of users whose free/busy details are retained for the + given 'quota'. + """ + + filename = self.get_object_in_store(quota, "freebusy") + if not filename or not isdir(filename): + return [] + + return listdir(filename) + + def get_freebusy(self, quota, user, mutable=False): "Get free/busy details for the given 'quota' and 'user'." filename = self.get_object_in_store(quota, "freebusy", user) - if not filename or not isfile(filename): - return [] - return map(lambda t: FreeBusyPeriod(*t), - self._get_table_atomic(quota, filename)) + if not filename or not isfile(filename): + periods = [] + else: + periods = map(lambda t: FreeBusyPeriod(*t), + self._get_table_atomic(quota, filename)) + + return FreeBusyCollection(periods, mutable) def set_freebusy(self, quota, user, freebusy): @@ -826,12 +907,12 @@ return False self._set_table_atomic(quota, filename, - map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) + map(lambda fb: fb.as_tuple(strings_only=True), list(freebusy))) return True # Journal entry methods. - def get_entries(self, quota, group): + def get_entries(self, quota, group, mutable=False): """ Return a list of journal entries for the given 'quota' for the indicated @@ -839,11 +920,14 @@ """ filename = self.get_object_in_store(quota, "journal", group) - if not filename or not isfile(filename): - return [] - return map(lambda t: FreeBusyPeriod(*t), - self._get_table_atomic(quota, filename)) + if not filename or not isfile(filename): + periods = [] + else: + periods = map(lambda t: FreeBusyPeriod(*t), + self._get_table_atomic(quota, filename)) + + return FreeBusyCollection(periods, mutable) def set_entries(self, quota, group, entries): @@ -857,7 +941,7 @@ return False self._set_table_atomic(quota, filename, - map(lambda fb: fb.as_tuple(strings_only=True), entries)) + map(lambda fb: fb.as_tuple(strings_only=True), list(entries))) return True # vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 imipweb/calendar.py --- a/imipweb/calendar.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imipweb/calendar.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ A Web interface to an event calendar. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -26,7 +26,6 @@ get_start_of_next_day, get_timestamp, ends_on_same_day, \ to_date, to_timezone from imiptools.period import add_day_start_points, add_empty_days, add_slots, \ - get_overlapping, \ get_scale, get_slots, get_spans, partition_by_day, \ remove_end_slot, Period, Point from imipweb.resource import FormUtilities, ResourceClient @@ -323,8 +322,8 @@ view_end = view_period.get_end() duration = view_period.get_duration() - preceding_events = view_start and get_overlapping(freebusy, Period(None, view_start, self.get_tzid())) or [] - following_events = view_end and get_overlapping(freebusy, Period(view_end, None, self.get_tzid())) or [] + preceding_events = view_start and freebusy.get_overlapping(Period(None, view_start, self.get_tzid())) or [] + following_events = view_end and freebusy.get_overlapping(Period(view_end, None, self.get_tzid())) or [] last_preceding = preceding_events and to_date(preceding_events[-1].get_end()) + timedelta(1) or None first_following = following_events and to_date(following_events[0].get_start()) or None @@ -487,7 +486,7 @@ # Filter periods outside the given view. if view_period: - periods = get_overlapping(periods, view_period) + periods = periods.get_overlapping(view_period) # Get the time scale with start and end points. diff -r 35958ffd9f83 -r 52476453b1e3 imipweb/event.py --- a/imipweb/event.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imipweb/event.py Fri Apr 22 16:22:58 2016 +0200 @@ -3,7 +3,7 @@ """ A Web interface to a calendar event. -Copyright (C) 2014, 2015 Paul Boddie +Copyright (C) 2014, 2015, 2016 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 @@ -23,7 +23,6 @@ uri_parts, uri_values from imiptools.dates import format_datetime, to_timezone from imiptools.mail import Messenger -from imiptools.period import have_conflict from imipweb.data import EventPeriod, event_period_from_period, FormPeriod, PeriodError from imipweb.resource import DateTimeFormUtilities, FormUtilities, ResourceClientForObject @@ -713,7 +712,7 @@ partstat = participant_attr and participant_attr.get("PARTSTAT") recurrences = self.obj.get_recurrence_start_points(recurrenceids, tzid) - for p in have_conflict(freebusy, periods, True): + for p in freebusy.have_conflict(periods, True): if not self.recurrenceid and p.is_replaced(recurrences): continue diff -r 35958ffd9f83 -r 52476453b1e3 imipweb/resource.py --- a/imipweb/resource.py Tue Apr 19 21:20:57 2016 +0200 +++ b/imipweb/resource.py Fri Apr 22 16:22:58 2016 +0200 @@ -23,6 +23,7 @@ from imiptools.client import Client, ClientForObject from imiptools.data import get_uri from imiptools.dates import format_datetime, to_date +from imiptools.period import FreeBusyCollection from imipweb.data import event_period_from_period, form_period_from_period, \ FormDate, PeriodError from imipweb.env import CGIEnvironment @@ -151,7 +152,7 @@ "Return a list of periods comprising the request summary." - summary = [] + summary = FreeBusyCollection() for uid, recurrenceid, request_type in self._get_requests(): diff -r 35958ffd9f83 -r 52476453b1e3 messages/da_DK.imip-agent.po --- a/messages/da_DK.imip-agent.po Tue Apr 19 21:20:57 2016 +0200 +++ b/messages/da_DK.imip-agent.po Fri Apr 22 16:22:58 2016 +0200 @@ -6,8 +6,8 @@ msgid "" msgstr "" "Project-Id-Version: 71b35ab1f0bd+\n" -"Report-Msgid-Bugs-To: Jonas Smedegaard \n" -"POT-Creation-Date: 2015-11-05 23:53+0100\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2016-04-18 21:06+0200\n" "PO-Revision-Date: 2016-04-08 09:19+0200\n" "Last-Translator: Jonas Smedegaard \n" "Language-Team: Danish\n" @@ -16,7 +16,7 @@ "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: imiptools/handlers/__init__.py:77 +#: imiptools/handlers/__init__.py:78 #, python-format msgid "" "If your mail program cannot handle this message, you may view the details " @@ -173,272 +173,272 @@ msgid "Do not participate" msgstr "Deltag ikke" -#: imipweb/calendar.py:133 +#: imipweb/calendar.py:132 #, python-format msgid "New event at %s" msgstr "Ny begivenhed ved %s" -#: imipweb/calendar.py:231 +#: imipweb/calendar.py:230 msgid "Pending requests:" msgstr "Afventende forespørgsler:" -#: imipweb/calendar.py:253 +#: imipweb/calendar.py:252 msgid "There are no pending requests." msgstr "Der er ingen afventende forespørgsler." -#: imipweb/calendar.py:270 +#: imipweb/calendar.py:269 msgid "Participants for scheduling:" msgstr "Deltagere til gruppeplanlægning:" -#: imipweb/calendar.py:275 imipweb/event.py:426 imipweb/event.py:525 +#: imipweb/calendar.py:274 imipweb/event.py:425 imipweb/event.py:524 msgid "Remove" msgstr "Fjern" -#: imipweb/calendar.py:280 +#: imipweb/calendar.py:279 msgid "Add" msgstr "Tilføj" -#: imipweb/calendar.py:304 +#: imipweb/calendar.py:303 msgid "Select days or periods for a new event." msgstr "Vælg dage eller perioder til en ny begivenhed." -#: imipweb/calendar.py:305 +#: imipweb/calendar.py:304 msgid "Hide busy time periods" msgstr "Skjul optagede perioder" -#: imipweb/calendar.py:306 +#: imipweb/calendar.py:305 msgid "Show busy time periods" msgstr "Vis optagede perioder" -#: imipweb/calendar.py:307 +#: imipweb/calendar.py:306 msgid "Show empty days" msgstr "Vis ledige dage" -#: imipweb/calendar.py:308 +#: imipweb/calendar.py:307 msgid "Hide empty days" msgstr "Skjul ledige dage" -#: imipweb/calendar.py:309 imipweb/calendar.py:846 +#: imipweb/calendar.py:308 imipweb/calendar.py:845 msgid "Clear selections" msgstr "Nulstil valgte perioder" -#: imipweb/calendar.py:339 +#: imipweb/calendar.py:338 msgid "Show earlier events" msgstr "Vis tidligere begivenheder" -#: imipweb/calendar.py:345 +#: imipweb/calendar.py:344 msgid "Show earlier" msgstr "Vis tidligere" -#: imipweb/calendar.py:354 +#: imipweb/calendar.py:353 msgid "Show later" msgstr "Vis senere" -#: imipweb/calendar.py:361 +#: imipweb/calendar.py:360 msgid "Show later events" msgstr "Vis senere begivenhder" -#: imipweb/calendar.py:440 +#: imipweb/calendar.py:439 #, python-format msgid "Showing events from %(start)s until %(end)s" msgstr "Viser begivenheder fra %(start)s frem til %(end)s" -#: imipweb/calendar.py:444 +#: imipweb/calendar.py:443 #, python-format msgid "Showing events from %s" msgstr "Viser begivenheder fra %s" -#: imipweb/calendar.py:446 +#: imipweb/calendar.py:445 #, python-format msgid "Showing events until %s" msgstr "Viser begivenheder frem til %s" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Pending requests" msgstr "Forespørgsler som venter" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Your schedule" msgstr "Din tidsplan" -#: imipweb/calendar.py:589 +#: imipweb/calendar.py:588 msgid "Calendar" msgstr "Kalender" -#: imipweb/calendar.py:840 +#: imipweb/calendar.py:839 msgid "Summary:" msgstr "Opsummering:" -#: imipweb/calendar.py:842 +#: imipweb/calendar.py:841 msgid "New event" msgstr "Ny begivenhed" -#: imipweb/calendar.py:993 +#: imipweb/calendar.py:992 msgid "(Participant is busy)" msgstr "(Deltager er optaget)" -#: imipweb/calendar.py:1076 +#: imipweb/calendar.py:1075 msgid "Select/deselect period" msgstr "Vælg/nulstil periode" -#: imipweb/event.py:44 +#: imipweb/event.py:43 msgid "Summary" msgstr "Opsummering" -#: imipweb/event.py:45 imipweb/event.py:629 imipweb/event.py:742 +#: imipweb/event.py:44 imipweb/event.py:628 imipweb/event.py:741 msgid "Start" msgstr "Start" -#: imipweb/event.py:46 imipweb/event.py:630 imipweb/event.py:743 +#: imipweb/event.py:45 imipweb/event.py:629 imipweb/event.py:742 msgid "End" msgstr "Slut" -#: imipweb/event.py:47 +#: imipweb/event.py:46 msgid "Organiser" msgstr "Arrangør" -#: imipweb/event.py:48 imipweb/event.py:595 +#: imipweb/event.py:47 imipweb/event.py:594 msgid "Attendee" msgstr "Deltager" -#: imipweb/event.py:52 +#: imipweb/event.py:51 msgid "Not confirmed" msgstr "Ikke bekræftet" -#: imipweb/event.py:53 +#: imipweb/event.py:52 msgid "Attending" msgstr "Deltager" -#: imipweb/event.py:54 +#: imipweb/event.py:53 msgid "Tentatively attending" msgstr "Deltager måske" -#: imipweb/event.py:55 +#: imipweb/event.py:54 msgid "Not attending" msgstr "Deltager ikke" -#: imipweb/event.py:56 +#: imipweb/event.py:55 msgid "Delegated" msgstr "Er delegeret til en anden" -#: imipweb/event.py:57 +#: imipweb/event.py:56 msgid "Not indicated" msgstr "Ikke angivet" -#: imipweb/event.py:154 +#: imipweb/event.py:153 msgid "This event has not been shared." msgstr "Denne begivenhed er ikke blevet delt med andre." -#: imipweb/event.py:159 +#: imipweb/event.py:158 msgid "An action is required for this request:" msgstr "Der skal tages et valg for denne forespørgsel:" -#: imipweb/event.py:162 +#: imipweb/event.py:161 msgid "Send reply" msgstr "Send svar" -#: imipweb/event.py:164 imipweb/event.py:183 +#: imipweb/event.py:163 imipweb/event.py:182 msgid "Discard event" msgstr "Drop begivenheden" -#: imipweb/event.py:166 imipweb/event.py:186 +#: imipweb/event.py:165 imipweb/event.py:185 msgid "Return to the calendar" msgstr "Tilbage til kalenderen" -#: imipweb/event.py:170 +#: imipweb/event.py:169 msgid "As organiser, you can perform the following:" msgstr "Som arrangør kan du gøre følgende:" -#: imipweb/event.py:173 +#: imipweb/event.py:172 msgid "Update event" msgstr "Opdatér begivenheden" -#: imipweb/event.py:177 +#: imipweb/event.py:176 msgid "Ignore counter-proposals" msgstr "Ignorér modforslag" -#: imipweb/event.py:181 +#: imipweb/event.py:180 msgid "Cancel event" msgstr "Aflys begivenheden" -#: imipweb/event.py:188 +#: imipweb/event.py:187 msgid "Save without sending" msgstr "Gem uden at sende" -#: imipweb/event.py:227 imipweb/event.py:741 imipweb/event.py:1290 +#: imipweb/event.py:226 imipweb/event.py:740 imipweb/event.py:1289 msgid "Event" msgstr "Begivenhed" -#: imipweb/event.py:274 +#: imipweb/event.py:273 msgid "First occurrence replaced by a separate event" msgstr "Den første periode er erstattet af en anden begivenhed" -#: imipweb/event.py:283 +#: imipweb/event.py:282 msgid "First occurrence excluded" msgstr "Den første periode blev fjernet" -#: imipweb/event.py:294 +#: imipweb/event.py:293 msgid "Add a recurrence" msgstr "Tilføj en ny periode" -#: imipweb/event.py:335 +#: imipweb/event.py:334 msgid "Add attendee" msgstr "Tilføj en deltager" -#: imipweb/event.py:428 +#: imipweb/event.py:427 msgid "(Uninvited)" msgstr "(Ikke inviteret)" -#: imipweb/event.py:429 +#: imipweb/event.py:428 msgid "Re-invite" msgstr "Invitér påny" -#: imipweb/event.py:453 +#: imipweb/event.py:452 msgid "This event modifies a recurring event." msgstr "Denne begivenhed ændrer en periode i en begivenhed som gentager sig." -#: imipweb/event.py:463 +#: imipweb/event.py:462 #, python-format msgid "This event occurs on the following occasions within the next %d days:" msgstr "Denne begivenhed forekommer på følgende anledninger de næste %d dage:" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence" msgstr "Forekomst" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence from rule" msgstr "Forekomst fra regel" -#: imipweb/event.py:527 +#: imipweb/event.py:526 msgid "(Removed)" msgstr "(Fjernet)" -#: imipweb/event.py:528 +#: imipweb/event.py:527 msgid "Re-add" msgstr "Tilføj påny" -#: imipweb/event.py:590 +#: imipweb/event.py:589 msgid "The following attendees have been suggested for this event:" msgstr "Følgende deltagere er blevet foreslået for denne begivenhed:" -#: imipweb/event.py:596 imipweb/event.py:626 +#: imipweb/event.py:595 imipweb/event.py:625 msgid "Suggested by..." msgstr "Foreslået af..." -#: imipweb/event.py:620 +#: imipweb/event.py:619 msgid "The following periods have been suggested for this event:" msgstr "Følgende perioder er blevet foreslået for denne begivenhed:" -#: imipweb/event.py:625 +#: imipweb/event.py:624 msgid "Periods" msgstr "Perioder" -#: imipweb/event.py:736 +#: imipweb/event.py:735 msgid "This event conflicts with others:" msgstr "Begivenheden er i konflikt med andre i tidsplanen:" -#: imipweb/event.py:763 +#: imipweb/event.py:762 msgid "(Unspecified event)" msgstr "(Ikke angivet begivenhed)" diff -r 35958ffd9f83 -r 52476453b1e3 messages/en_GB.imip-agent.po --- a/messages/en_GB.imip-agent.po Tue Apr 19 21:20:57 2016 +0200 +++ b/messages/en_GB.imip-agent.po Fri Apr 22 16:22:58 2016 +0200 @@ -167,272 +167,272 @@ msgid "Do not participate" msgstr "" -#: imipweb/calendar.py:133 +#: imipweb/calendar.py:132 #, python-format msgid "New event at %s" msgstr "" -#: imipweb/calendar.py:231 +#: imipweb/calendar.py:230 msgid "Pending requests:" msgstr "" -#: imipweb/calendar.py:253 +#: imipweb/calendar.py:252 msgid "There are no pending requests." msgstr "" -#: imipweb/calendar.py:270 +#: imipweb/calendar.py:269 msgid "Participants for scheduling:" msgstr "" -#: imipweb/calendar.py:275 imipweb/event.py:426 imipweb/event.py:525 +#: imipweb/calendar.py:274 imipweb/event.py:425 imipweb/event.py:524 msgid "Remove" msgstr "" -#: imipweb/calendar.py:280 +#: imipweb/calendar.py:279 msgid "Add" msgstr "" +#: imipweb/calendar.py:303 +msgid "Select days or periods for a new event." +msgstr "" + #: imipweb/calendar.py:304 -msgid "Select days or periods for a new event." +msgid "Hide busy time periods" msgstr "" #: imipweb/calendar.py:305 -msgid "Hide busy time periods" +msgid "Show busy time periods" msgstr "" #: imipweb/calendar.py:306 -msgid "Show busy time periods" +msgid "Show empty days" msgstr "" #: imipweb/calendar.py:307 -msgid "Show empty days" -msgstr "" - -#: imipweb/calendar.py:308 msgid "Hide empty days" msgstr "" -#: imipweb/calendar.py:309 imipweb/calendar.py:846 +#: imipweb/calendar.py:308 imipweb/calendar.py:845 msgid "Clear selections" msgstr "" -#: imipweb/calendar.py:339 +#: imipweb/calendar.py:338 msgid "Show earlier events" msgstr "" -#: imipweb/calendar.py:345 +#: imipweb/calendar.py:344 msgid "Show earlier" msgstr "" -#: imipweb/calendar.py:354 +#: imipweb/calendar.py:353 msgid "Show later" msgstr "" -#: imipweb/calendar.py:361 +#: imipweb/calendar.py:360 msgid "Show later events" msgstr "" -#: imipweb/calendar.py:440 +#: imipweb/calendar.py:439 #, python-format msgid "Showing events from %(start)s until %(end)s" msgstr "" -#: imipweb/calendar.py:444 +#: imipweb/calendar.py:443 #, python-format msgid "Showing events from %s" msgstr "" -#: imipweb/calendar.py:446 +#: imipweb/calendar.py:445 #, python-format msgid "Showing events until %s" msgstr "" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Pending requests" msgstr "" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Your schedule" msgstr "" -#: imipweb/calendar.py:589 +#: imipweb/calendar.py:588 msgid "Calendar" msgstr "" -#: imipweb/calendar.py:840 +#: imipweb/calendar.py:839 msgid "Summary:" msgstr "" -#: imipweb/calendar.py:842 +#: imipweb/calendar.py:841 msgid "New event" msgstr "" -#: imipweb/calendar.py:993 +#: imipweb/calendar.py:992 msgid "(Participant is busy)" msgstr "" -#: imipweb/calendar.py:1076 +#: imipweb/calendar.py:1075 msgid "Select/deselect period" msgstr "" -#: imipweb/event.py:44 +#: imipweb/event.py:43 msgid "Summary" msgstr "" +#: imipweb/event.py:44 imipweb/event.py:628 imipweb/event.py:741 +msgid "Start" +msgstr "" + #: imipweb/event.py:45 imipweb/event.py:629 imipweb/event.py:742 -msgid "Start" -msgstr "" - -#: imipweb/event.py:46 imipweb/event.py:630 imipweb/event.py:743 msgid "End" msgstr "" -#: imipweb/event.py:47 +#: imipweb/event.py:46 msgid "Organiser" msgstr "" -#: imipweb/event.py:48 imipweb/event.py:595 +#: imipweb/event.py:47 imipweb/event.py:594 msgid "Attendee" msgstr "" +#: imipweb/event.py:51 +msgid "Not confirmed" +msgstr "" + #: imipweb/event.py:52 -msgid "Not confirmed" +msgid "Attending" msgstr "" #: imipweb/event.py:53 -msgid "Attending" +msgid "Tentatively attending" msgstr "" #: imipweb/event.py:54 -msgid "Tentatively attending" +msgid "Not attending" msgstr "" #: imipweb/event.py:55 -msgid "Not attending" +msgid "Delegated" msgstr "" #: imipweb/event.py:56 -msgid "Delegated" -msgstr "" - -#: imipweb/event.py:57 msgid "Not indicated" msgstr "" -#: imipweb/event.py:154 +#: imipweb/event.py:153 msgid "This event has not been shared." msgstr "" -#: imipweb/event.py:159 +#: imipweb/event.py:158 msgid "An action is required for this request:" msgstr "" -#: imipweb/event.py:162 +#: imipweb/event.py:161 msgid "Send reply" msgstr "" -#: imipweb/event.py:164 imipweb/event.py:183 +#: imipweb/event.py:163 imipweb/event.py:182 msgid "Discard event" msgstr "" -#: imipweb/event.py:166 imipweb/event.py:186 +#: imipweb/event.py:165 imipweb/event.py:185 msgid "Return to the calendar" msgstr "" -#: imipweb/event.py:170 +#: imipweb/event.py:169 msgid "As organiser, you can perform the following:" msgstr "" -#: imipweb/event.py:173 +#: imipweb/event.py:172 msgid "Update event" msgstr "" -#: imipweb/event.py:177 +#: imipweb/event.py:176 msgid "Ignore counter-proposals" msgstr "" -#: imipweb/event.py:181 +#: imipweb/event.py:180 msgid "Cancel event" msgstr "" -#: imipweb/event.py:188 +#: imipweb/event.py:187 msgid "Save without sending" msgstr "" -#: imipweb/event.py:227 imipweb/event.py:741 imipweb/event.py:1290 +#: imipweb/event.py:226 imipweb/event.py:740 imipweb/event.py:1289 msgid "Event" msgstr "" -#: imipweb/event.py:274 +#: imipweb/event.py:273 msgid "First occurrence replaced by a separate event" msgstr "" -#: imipweb/event.py:283 +#: imipweb/event.py:282 msgid "First occurrence excluded" msgstr "" -#: imipweb/event.py:294 +#: imipweb/event.py:293 msgid "Add a recurrence" msgstr "" -#: imipweb/event.py:335 +#: imipweb/event.py:334 msgid "Add attendee" msgstr "" +#: imipweb/event.py:427 +msgid "(Uninvited)" +msgstr "" + #: imipweb/event.py:428 -msgid "(Uninvited)" -msgstr "" - -#: imipweb/event.py:429 msgid "Re-invite" msgstr "" -#: imipweb/event.py:453 +#: imipweb/event.py:452 msgid "This event modifies a recurring event." msgstr "" -#: imipweb/event.py:463 +#: imipweb/event.py:462 #, python-format msgid "This event occurs on the following occasions within the next %d days:" msgstr "" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence" msgstr "" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence from rule" msgstr "" +#: imipweb/event.py:526 +msgid "(Removed)" +msgstr "" + #: imipweb/event.py:527 -msgid "(Removed)" -msgstr "" - -#: imipweb/event.py:528 msgid "Re-add" msgstr "" -#: imipweb/event.py:590 +#: imipweb/event.py:589 msgid "The following attendees have been suggested for this event:" msgstr "" -#: imipweb/event.py:596 imipweb/event.py:626 +#: imipweb/event.py:595 imipweb/event.py:625 msgid "Suggested by..." msgstr "" -#: imipweb/event.py:620 +#: imipweb/event.py:619 msgid "The following periods have been suggested for this event:" msgstr "" -#: imipweb/event.py:625 +#: imipweb/event.py:624 msgid "Periods" msgstr "" -#: imipweb/event.py:736 +#: imipweb/event.py:735 msgid "This event conflicts with others:" msgstr "" -#: imipweb/event.py:763 +#: imipweb/event.py:762 msgid "(Unspecified event)" msgstr "" diff -r 35958ffd9f83 -r 52476453b1e3 messages/nb_NO.imip-agent.po --- a/messages/nb_NO.imip-agent.po Tue Apr 19 21:20:57 2016 +0200 +++ b/messages/nb_NO.imip-agent.po Fri Apr 22 16:22:58 2016 +0200 @@ -174,272 +174,272 @@ msgid "Do not participate" msgstr "Ikke delta" -#: imipweb/calendar.py:133 +#: imipweb/calendar.py:132 #, python-format msgid "New event at %s" msgstr "Ny hendelse på %s" -#: imipweb/calendar.py:231 +#: imipweb/calendar.py:230 msgid "Pending requests:" msgstr "Forespørsler som venter" -#: imipweb/calendar.py:253 +#: imipweb/calendar.py:252 msgid "There are no pending requests." msgstr "Ingen forespørsler" -#: imipweb/calendar.py:270 +#: imipweb/calendar.py:269 msgid "Participants for scheduling:" msgstr "Deltakere for gruppeplanlegging:" -#: imipweb/calendar.py:275 imipweb/event.py:426 imipweb/event.py:525 +#: imipweb/calendar.py:274 imipweb/event.py:425 imipweb/event.py:524 msgid "Remove" msgstr "Fjerne" -#: imipweb/calendar.py:280 +#: imipweb/calendar.py:279 msgid "Add" msgstr "Legge til" -#: imipweb/calendar.py:304 +#: imipweb/calendar.py:303 msgid "Select days or periods for a new event." msgstr "Velg dager eller perioder for en ny hendelse." -#: imipweb/calendar.py:305 +#: imipweb/calendar.py:304 msgid "Hide busy time periods" msgstr "Skjule opptatte perioder" -#: imipweb/calendar.py:306 +#: imipweb/calendar.py:305 msgid "Show busy time periods" msgstr "Vise opptatte perioder" -#: imipweb/calendar.py:307 +#: imipweb/calendar.py:306 msgid "Show empty days" msgstr "Vise ledige dager" -#: imipweb/calendar.py:308 +#: imipweb/calendar.py:307 msgid "Hide empty days" msgstr "Skjule ledige dager" -#: imipweb/calendar.py:309 imipweb/calendar.py:846 +#: imipweb/calendar.py:308 imipweb/calendar.py:845 msgid "Clear selections" msgstr "Nullstille valgte perioder" -#: imipweb/calendar.py:339 +#: imipweb/calendar.py:338 msgid "Show earlier events" msgstr "Tidligere hendelser" -#: imipweb/calendar.py:345 +#: imipweb/calendar.py:344 msgid "Show earlier" msgstr "Tidligere" -#: imipweb/calendar.py:354 +#: imipweb/calendar.py:353 msgid "Show later" msgstr "Senere" -#: imipweb/calendar.py:361 +#: imipweb/calendar.py:360 msgid "Show later events" msgstr "Senere hendelser" -#: imipweb/calendar.py:440 +#: imipweb/calendar.py:439 #, python-format msgid "Showing events from %(start)s until %(end)s" msgstr "Viser hendelser fra %(start)s frem til %(end)s" -#: imipweb/calendar.py:444 +#: imipweb/calendar.py:443 #, python-format msgid "Showing events from %s" msgstr "Viser hendelser fra %s" -#: imipweb/calendar.py:446 +#: imipweb/calendar.py:445 #, python-format msgid "Showing events until %s" msgstr "Viser hendelser frem til %s" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Pending requests" msgstr "Forespørsler som venter" -#: imipweb/calendar.py:470 +#: imipweb/calendar.py:469 msgid "Your schedule" msgstr "Din tidsplan" -#: imipweb/calendar.py:589 +#: imipweb/calendar.py:588 msgid "Calendar" msgstr "Kalender" -#: imipweb/calendar.py:840 +#: imipweb/calendar.py:839 msgid "Summary:" msgstr "Sammendrag:" -#: imipweb/calendar.py:842 +#: imipweb/calendar.py:841 msgid "New event" msgstr "Ny hendelse" -#: imipweb/calendar.py:993 +#: imipweb/calendar.py:992 msgid "(Participant is busy)" msgstr "(Deltaker er opptatt)" -#: imipweb/calendar.py:1076 +#: imipweb/calendar.py:1075 msgid "Select/deselect period" msgstr "Velge/nullstille periode" -#: imipweb/event.py:44 +#: imipweb/event.py:43 msgid "Summary" msgstr "Sammendrag" -#: imipweb/event.py:45 imipweb/event.py:629 imipweb/event.py:742 +#: imipweb/event.py:44 imipweb/event.py:628 imipweb/event.py:741 msgid "Start" msgstr "Start" -#: imipweb/event.py:46 imipweb/event.py:630 imipweb/event.py:743 +#: imipweb/event.py:45 imipweb/event.py:629 imipweb/event.py:742 msgid "End" msgstr "Slutt" -#: imipweb/event.py:47 +#: imipweb/event.py:46 msgid "Organiser" msgstr "Arrangør" -#: imipweb/event.py:48 imipweb/event.py:595 +#: imipweb/event.py:47 imipweb/event.py:594 msgid "Attendee" msgstr "Deltaker" -#: imipweb/event.py:52 +#: imipweb/event.py:51 msgid "Not confirmed" msgstr "Ikke bekreftet" -#: imipweb/event.py:53 +#: imipweb/event.py:52 msgid "Attending" msgstr "Deltar" -#: imipweb/event.py:54 +#: imipweb/event.py:53 msgid "Tentatively attending" msgstr "Delta kanskje" -#: imipweb/event.py:55 +#: imipweb/event.py:54 msgid "Not attending" msgstr "Deltar ikke" -#: imipweb/event.py:56 +#: imipweb/event.py:55 msgid "Delegated" msgstr "Har delegert til en annen" -#: imipweb/event.py:57 +#: imipweb/event.py:56 msgid "Not indicated" msgstr "Ikke angitt" -#: imipweb/event.py:154 +#: imipweb/event.py:153 msgid "This event has not been shared." msgstr "Denne hendelsen er ikke blitt delt med andre." -#: imipweb/event.py:159 +#: imipweb/event.py:158 msgid "An action is required for this request:" msgstr "Et valg må tas med denne forespørselen:" -#: imipweb/event.py:162 +#: imipweb/event.py:161 msgid "Send reply" msgstr "Send svar" -#: imipweb/event.py:164 imipweb/event.py:183 +#: imipweb/event.py:163 imipweb/event.py:182 msgid "Discard event" msgstr "Kaste hendelsen" -#: imipweb/event.py:166 imipweb/event.py:186 +#: imipweb/event.py:165 imipweb/event.py:185 msgid "Return to the calendar" msgstr "Tilbake til kalenderen" -#: imipweb/event.py:170 +#: imipweb/event.py:169 msgid "As organiser, you can perform the following:" msgstr "Som arrangør kan du utføre følgende:" -#: imipweb/event.py:173 +#: imipweb/event.py:172 msgid "Update event" msgstr "Oppdatere hendelsen" -#: imipweb/event.py:177 +#: imipweb/event.py:176 msgid "Ignore counter-proposals" msgstr "Overse motforslag" -#: imipweb/event.py:181 +#: imipweb/event.py:180 msgid "Cancel event" msgstr "Send avbud for hendelsen" -#: imipweb/event.py:188 +#: imipweb/event.py:187 msgid "Save without sending" msgstr "Lagre uten å sende" -#: imipweb/event.py:227 imipweb/event.py:741 imipweb/event.py:1290 +#: imipweb/event.py:226 imipweb/event.py:740 imipweb/event.py:1289 msgid "Event" msgstr "Hendelse" -#: imipweb/event.py:274 +#: imipweb/event.py:273 msgid "First occurrence replaced by a separate event" msgstr "Den første perioden er erstattet med en annen hendelse" -#: imipweb/event.py:283 +#: imipweb/event.py:282 msgid "First occurrence excluded" msgstr "Den første perioden ble fjernet" -#: imipweb/event.py:294 +#: imipweb/event.py:293 msgid "Add a recurrence" msgstr "Legge til en ny periode" -#: imipweb/event.py:335 +#: imipweb/event.py:334 msgid "Add attendee" msgstr "Legge til en deltaker" -#: imipweb/event.py:428 +#: imipweb/event.py:427 msgid "(Uninvited)" msgstr "(Ikke invitert)" -#: imipweb/event.py:429 +#: imipweb/event.py:428 msgid "Re-invite" msgstr "Invitere på nytt" -#: imipweb/event.py:453 +#: imipweb/event.py:452 msgid "This event modifies a recurring event." msgstr "Denne hendelsen endrer en period i en hendelse som gjentar seg." -#: imipweb/event.py:463 +#: imipweb/event.py:462 #, python-format msgid "This event occurs on the following occasions within the next %d days:" msgstr "Denne hendelsen forekommer på følgende anledninger innen %d dager:" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence" msgstr "Forekomst" -#: imipweb/event.py:494 +#: imipweb/event.py:493 msgid "Occurrence from rule" msgstr "Forekomst fra regel" -#: imipweb/event.py:527 +#: imipweb/event.py:526 msgid "(Removed)" msgstr "(Fjernet)" -#: imipweb/event.py:528 +#: imipweb/event.py:527 msgid "Re-add" msgstr "Legge til på nytt" -#: imipweb/event.py:590 +#: imipweb/event.py:589 msgid "The following attendees have been suggested for this event:" msgstr "Følgende deltakere er blitt foreslått for denne hendelsen:" -#: imipweb/event.py:596 imipweb/event.py:626 +#: imipweb/event.py:595 imipweb/event.py:625 msgid "Suggested by..." msgstr "Foreslått av..." -#: imipweb/event.py:620 +#: imipweb/event.py:619 msgid "The following periods have been suggested for this event:" msgstr "Følgende perioder er blitt foreslått for denne hendelsen:" -#: imipweb/event.py:625 +#: imipweb/event.py:624 msgid "Periods" msgstr "Perioder" -#: imipweb/event.py:736 +#: imipweb/event.py:735 msgid "This event conflicts with others:" msgstr "Hendelsen er i konflikt med andre i tidsplanen:" -#: imipweb/event.py:763 +#: imipweb/event.py:762 msgid "(Unspecified event)" msgstr "(Ikke angitt hendelse)" diff -r 35958ffd9f83 -r 52476453b1e3 test_all.sh --- a/test_all.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/test_all.sh Fri Apr 22 16:22:58 2016 +0200 @@ -1,3 +1,6 @@ #!/bin/sh -for FILENAME in tests/test_*.sh ; do echo $FILENAME ; $FILENAME ; done +for FILENAME in tests/test_*.sh ; do + echo "$FILENAME" + "$FILENAME" +done diff -r 35958ffd9f83 -r 52476453b1e3 tests/common.sh --- a/tests/common.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/common.sh Fri Apr 22 16:22:58 2016 +0200 @@ -1,47 +1,44 @@ #!/bin/sh -THIS_DIR=`dirname "$0"` -BASE_DIR="$THIS_DIR/.." - -STORE=/tmp/store -STATIC=/tmp/static -PREFS=/tmp/prefs -JOURNAL=/tmp/journal - -ARGS="-S $STORE -P $STATIC -p $PREFS -j $JOURNAL -d" +. "`dirname \"$0\"`/common_minimal.sh" ACCEPT_SCRIPT="$THIS_DIR/test_handle.py" -ACCEPT_ARGS="accept $STORE $JOURNAL $PREFS" +ACCEPT_ARGS="accept $STORE_TYPE $STORE $JOURNAL $PREFS" COUNTER_SCRIPT="$THIS_DIR/test_handle.py" -COUNTER_ARGS="counter $STORE $JOURNAL $PREFS" +COUNTER_ARGS="counter $STORE_TYPE $STORE $JOURNAL $PREFS" DECLINE_SCRIPT="$THIS_DIR/test_handle.py" -DECLINE_ARGS="decline $STORE $JOURNAL $PREFS" +DECLINE_ARGS="decline $STORE_TYPE $STORE $JOURNAL $PREFS" FREEBUSY_SCRIPT="$BASE_DIR/tools/make_freebusy.py" FREEBUSY_ARGS="-s -n" +LIST_SCRIPT="$THIS_DIR/list_table.py" +LIST_ARGS="$STORE_TYPE $STORE $JOURNAL" + OUTGOING_SCRIPT="$BASE_DIR/imip_person_outgoing.py" PERSON_SCRIPT="$BASE_DIR/imip_person.py" -RESOURCE_SCRIPT="$BASE_DIR/imip_resource.py" - -SHOWMAIL="$BASE_DIR/tools/showmail.py" +SET_QUOTA_LIMIT="$BASE_DIR/tools/set_quota_limit.py" +SET_QUOTA_LIMIT_ARGS="-T $STORE_TYPE -j $JOURNAL" TAB=`printf '\t'` -TEMPLATES="$THIS_DIR/templates" - -ERROR=err.tmp - PYTHONPATH="$BASE_DIR" export PYTHONPATH -rm -rf "$STORE" +if [ "$STORE_TYPE" = "file" ]; then + rm -rf "$STORE" + rm -rf "$JOURNAL" +elif [ "$STORE_TYPE" = "postgresql" ]; then + dropdb "$DBNAME" + createdb "$DBNAME" + psql -q -f "$BASE_DIR/conf/postgresql/schema.sql" "$DBNAME" +fi + rm -rf "$STATIC" rm -rf "$PREFS" -rm -rf "$JOURNAL" rm -f "$ERROR" rm -f out*.tmp diff -r 35958ffd9f83 -r 52476453b1e3 tests/common_minimal.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/common_minimal.sh Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,28 @@ +#!/bin/sh + +THIS_DIR=`dirname "$0"` +BASE_DIR="$THIS_DIR/.." + +STORE_TYPE=${STORE_TYPE:-file} + +if [ "$STORE_TYPE" = "file" ]; then + STORE=/tmp/store + JOURNAL=/tmp/journal +elif [ "$STORE_TYPE" = "postgresql" ]; then + DBNAME='imip_agent_test' + STORE="dbname=$DBNAME" + JOURNAL="$STORE" +fi + +STATIC=/tmp/static +PREFS=/tmp/prefs + +ARGS="-T $STORE_TYPE -S $STORE -P $STATIC -p $PREFS -j $JOURNAL -d" + +RESOURCE_SCRIPT="$BASE_DIR/imip_resource.py" + +SHOWMAIL="$BASE_DIR/tools/showmail.py" + +TEMPLATES="$THIS_DIR/templates" + +ERROR=err.tmp diff -r 35958ffd9f83 -r 52476453b1e3 tests/list_table.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/list_table.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,110 @@ +#!/usr/bin/env python + +""" +Show the contents of a table. + +Copyright (C) 2016 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.data import Object +from imiptools.stores import get_store, get_journal +import sys + +def show_list(data): + for row in data: + print row or "" + +def show_object(data): + if data: + print Object(data).to_string() + +def show_periods(data): + for row in data: + print "\t".join(row.as_tuple(strings_only=True)) + +def show_tuples(data): + for row in data: + print "\t".join([(column or "") for column in row]) + +if __name__ == "__main__": + try: + store_type, store_dir, journal_dir, user, table = sys.argv[1:6] + args = sys.argv[6:] + except ValueError: + print >>sys.stderr, """\ +Need a store type, a store directory, a journal directory, a user URI, and a +table to show. Other arguments may be needed for certain tables. +""" + sys.exit(1) + + store = get_store(store_type, store_dir) + journal = get_journal(store_type, journal_dir) + + # Periods. + + if table == "entries": + group = args[0] + data = journal.get_entries(user, group) + show_periods(data) + + elif table == "freebusy": + data = store.get_freebusy(user) + show_periods(data) + + elif table == "freebusy_offers": + data = store.get_freebusy_offers(user) + show_periods(data) + + elif table == "freebusy_other": + other = args[0] + data = store.get_freebusy_for_other(user, other) + show_periods(data) + + # Tuples. + + elif table == "requests": + data = store.get_requests(user) + show_tuples(data) + + elif table == "freebusy_providers": + data = store.get_freebusy_providers(user) + show_tuples(data) + + # Objects. + + elif table == "countered_object": + uid = args[0] + other = args[1] + data = store.get_counter(user, other, uid) + show_object(data) + + elif table == "object": + uid = args[0] + data = store.get_event(user, uid) + show_object(data) + + elif table == "recurrence": + uid = args[0] + recurrenceid = args[1] + data = store.get_event(user, uid, recurrenceid) + show_object(data) + + elif table == "cancelled_recurrences": + uid = args[0] + data = store.get_cancelled_recurrences(user, uid) + show_list(data) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 tests/resource_request.sh --- a/tests/resource_request.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/resource_request.sh Fri Apr 22 16:22:58 2016 +0200 @@ -1,17 +1,6 @@ #!/bin/sh -THIS_DIR=`dirname $0` - -TEMPLATES="$THIS_DIR/templates" -RESOURCE_SCRIPT="$THIS_DIR/../imip_resource.py" -SHOWMAIL="$THIS_DIR/../tools/showmail.py" -STORE=/tmp/store -STATIC=/tmp/static -PREFS=/tmp/prefs -JOURNAL=/tmp/journal -ARGS="-S $STORE -P $STATIC -p $PREFS -j $JOURNAL -d" - -ERROR=err.tmp +. "`dirname \"$0\"`/common_minimal.sh" export N=$1 export START=20141126T090000 diff -r 35958ffd9f83 -r 52476453b1e3 tests/templates/event-request-person-recurring-rdate.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/templates/event-request-person-recurring-rdate.txt Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,37 @@ +Content-Type: multipart/alternative; boundary="===============0047278175==" +MIME-Version: 1.0 +From: paul.boddie@example.com +To: vincent.vole@example.com, harvey.horse@example.com +Subject: Invitation! + +--===============0047278175== +Content-Type: text/plain; charset="us-ascii" +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit + +This message contains an event. + +--===============0047278175== +MIME-Version: 1.0 +Content-Transfer-Encoding: 7bit +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST" + +BEGIN:VCALENDAR +PRODID:-//imip-agent/test//EN +METHOD:REQUEST +VERSION:2.0 +BEGIN:VEVENT +ORGANIZER:mailto:paul.boddie@example.com +ATTENDEE;RSVP=TRUE:mailto:vincent.vole@example.com +ATTENDEE;RSVP=TRUE:mailto:harvey.horse@example.com +ATTENDEE;RSVP=TRUE:mailto:paul.boddie@example.com +DTSTAMP:20141009T182400Z +DTSTART;TZID=Europe/Oslo:20141010T100000 +DTEND;TZID=Europe/Oslo:20141010T110000 +RDATE;TZID=Europe/Oslo;VALUE=PERIOD:20141011T100000/20141011T110000 +SUMMARY:Recurring event +UID:event26@example.com +END:VEVENT +END:VCALENDAR + +--===============0047278175==-- diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_freebusy_publishing.sh --- a/tests/test_freebusy_publishing.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_freebusy_publishing.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,8 +4,6 @@ USER="mailto:paul.boddie@example.com" SENDER="mailto:resource-room-confroom@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -15,17 +13,23 @@ | "$SHOWMAIL" \ > out0.tmp - grep -q "^20140401T070000Z${TAB}20140401T080000Z" "$FBOTHERFILE" \ -&& grep -q "^20140401T080000Z${TAB}20140401T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out1.tmp + + grep -q "^20140401T070000Z${TAB}20140401T080000Z" "out1.tmp" \ +&& grep -q "^20140401T080000Z${TAB}20140401T100000Z" "out1.tmp" \ && echo "Success" \ || echo "Failed" "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/fb-publish-again.txt" 2>> $ERROR \ | "$SHOWMAIL" \ -> out0.tmp +> out2.tmp - grep -q "^20140401T070000Z${TAB}20140401T080000Z" "$FBOTHERFILE" \ -&& ! grep -q "^20140401T080000Z${TAB}20140401T100000Z" "$FBOTHERFILE" \ -&& grep -q "^20140401T083000Z${TAB}20140401T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out3.tmp + + grep -q "^20140401T070000Z${TAB}20140401T080000Z" "out3.tmp" \ +&& ! grep -q "^20140401T080000Z${TAB}20140401T100000Z" "out3.tmp" \ +&& grep -q "^20140401T083000Z${TAB}20140401T100000Z" "out3.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_handle.py --- a/tests/test_handle.py Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_handle.py Fri Apr 22 16:22:58 2016 +0200 @@ -24,7 +24,8 @@ from imiptools.dates import get_datetime, to_timezone from imiptools.mail import Messenger from imiptools.period import RecurringPeriod -import imiptools.stores.file +from imiptools.stores import get_store, get_journal +from os.path import split import sys class TestClient(ClientForObject): @@ -36,7 +37,7 @@ # Action methods. - def handle_request(self, action, start=None, end=None): + def handle_request(self, action, start=None, end=None, recurrenceid=None): """ Process the current request for the current user. Return whether the @@ -44,18 +45,36 @@ If 'start' and 'end' are specified, they will be used in any counter-proposal. + + Where 'recurrenceid' is specified and refers to a new recurrence, the + action will apply only to this new recurrence. """ + have_new_recurrence = self.obj.get_recurrenceid() != recurrenceid + + if have_new_recurrence: + self.obj["RECURRENCE-ID"] = [(recurrenceid, {})] + self.obj.remove_all(["RDATE", "RRULE"]) + # Reply only on behalf of this user. if action in ("accept", "decline"): attendee_attr = self.update_participation(action == "accept" and "ACCEPTED" or "DECLINED") method = "REPLY" - # For counter-proposals, set a new main period for the event. - elif action == "counter": attendee_attr = self.obj.get_value_map("ATTENDEE").get(self.user) + method = "COUNTER" + + # Nothing else is supported. + + else: + return None + + # For counter-proposals or new recurrences, set a new main period for + # the event. + + if action == "counter" or have_new_recurrence: period = self.obj.get_main_period(self.get_tzid()) # Use the existing or configured time zone for the specified @@ -65,9 +84,8 @@ end = to_timezone(get_datetime(end), period.tzid) period = RecurringPeriod(start, end, period.tzid, period.origin, period.get_start_attr(), period.get_end_attr()) self.obj.set_period(period) - method = "COUNTER" - else: - return None + + # Where no attendees remain, no message is generated. if not attendee_attr: return None @@ -93,39 +111,53 @@ # response message to standard output. if __name__ == "__main__": + progname = split(sys.argv[0])[-1] + try: - action, store_dir, journal_dir, preferences_dir, user = sys.argv[1:6] - if action == "counter": - start, end = sys.argv[6:8] - i = 8 + action, store_type, store_dir, journal_dir, preferences_dir, user = sys.argv[1:7] + if len(sys.argv) >= 10: + start, end = sys.argv[7:9] + i = 9 else: start, end = None, None - i = 6 + i = 7 uid, recurrenceid = (sys.argv[i:i+2] + [None] * 2)[:2] except ValueError: print >>sys.stderr, """\ -Need 'accept', 'counter' or 'decline', a store directory, a preferences -directory, user URI, any counter-proposal datetimes (see below), plus the -appropriate event UID and RECURRENCE-ID (if a recurrence is involved). +Usage: %s + [ ] + + +Need 'accept', 'counter' or 'decline', a store type, a store directory, a +journal directory, a preferences directory, user URI, any counter-proposal or +new recurrence datetimes (see below), plus the appropriate event UID and +RECURRENCE-ID (if a recurrence is involved). The RECURRENCE-ID must be in exactly the form employed by the store, not a -different but equivalent representation. +different but equivalent representation, if the identifier is to refer to an +existing recurrence. Alternatively, omit the UID and RECURRENCE-ID and provide event-only details on standard input to force the script to handle an event not already present in the store. If 'counter' has been indicated, alternative start and end datetimes are also -required. +required. If a specific recurrence is being separated from an event, such +datetimes are also required in order to set the main period of the recurrence. """ sys.exit(1) - store = imiptools.stores.file.FileStore(store_dir) - journal = imiptools.stores.file.FileJournal(journal_dir) + store = get_store(store_type, store_dir) + journal = get_journal(store_type, journal_dir) if uid is not None: fragment = store.get_event(user, uid, recurrenceid) + # Permit new recurrences by getting the parent object. + + if not fragment: + fragment = store.get_event(user, uid) + if not fragment: print >>sys.stderr, "No such event:", uid, recurrenceid sys.exit(1) @@ -134,7 +166,7 @@ obj = Object(fragment) handler = TestClient(obj, user, Messenger(), store, None, journal, preferences_dir) - response = handler.handle_request(action, start, end) + response = handler.handle_request(action, start, end, recurrenceid) if response: if uid is not None: diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_outgoing_invitation.sh --- a/tests/test_outgoing_invitation.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_outgoing_invitation.sh Fri Apr 22 16:22:58 2016 +0200 @@ -3,70 +3,84 @@ . "`dirname \"$0\"`/common.sh" USER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" echo 'share' > "$PREFS/$USER/freebusy_sharing" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request.txt" 2>> $ERROR -cp "$FBFILE" out1.tmp - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out1.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel.txt" 2>> $ERROR echo "Cancel..." -cp "$FBFILE" out2.tmp - ! grep -q '^2' "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2.tmp + + ! grep -q '^2' "out2.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring.txt" 2>> $ERROR -cp "$FBFILE" out3.tmp - [ `cat "$FBFILE" | wc -l` = '3' ] \ -&& grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3.tmp + + [ `cat "out3.tmp" | wc -l` = '3' ] \ +&& grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-recurring-instance.txt" 2>> $ERROR -cp "$FBFILE" out4.tmp - [ `cat "$FBFILE" | wc -l` = '2' ] \ -&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4.tmp + + [ `cat "out4.tmp" | wc -l` = '2' ] \ +&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "out4.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring-reschedule-instance.txt" 2>> $ERROR -cp "$FBFILE" out5.tmp - grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBFILE" \ -&& ! grep -q "^20141010T090000Z${TAB}20141010T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out5.tmp" \ +&& ! grep -q "^20141010T090000Z${TAB}20141010T100000Z" "out5.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-recurring.txt" 2>> $ERROR -cp "$FBFILE" out6.tmp - ! grep -q '^2' "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out6.tmp + + ! grep -q '^2' "out6.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring-day.txt" 2>> $ERROR -cp "$FBFILE" out7.tmp - [ `cat "$FBFILE" | wc -l` = '3' ] \ -&& grep -q "^20141211T230000Z${TAB}20141212T230000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out7.tmp + + [ `cat "out7.tmp" | wc -l` = '3' ] \ +&& grep -q "^20141211T230000Z${TAB}20141212T230000Z" "out7.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-recurring-day.txt" 2>> $ERROR -cp "$FBFILE" out8.tmp - ! grep -q '^2' "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out8.tmp + + ! grep -q '^2' "out8.tmp" \ && echo "Success" \ || echo "Failed" @@ -75,31 +89,39 @@ echo 'Europe/Mariehamn' > "$PREFS/$USER/TZID" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring.txt" 2>> $ERROR -cp "$FBFILE" out9.tmp - [ `cat "$FBFILE" | wc -l` = '3' ] \ -&& grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out9.tmp + + [ `cat "out9.tmp" | wc -l` = '3' ] \ +&& grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out9.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-recurring.txt" 2>> $ERROR -cp "$FBFILE" out10.tmp - ! grep -q '^2' "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out10.tmp + + ! grep -q '^2' "out10.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring-day.txt" 2>> $ERROR -cp "$FBFILE" out11.tmp - [ `cat "$FBFILE" | wc -l` = '3' ] \ -&& grep -q "^20141211T230000Z${TAB}20141212T230000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out11.tmp + + [ `cat "out11.tmp" | wc -l` = '3' ] \ +&& grep -q "^20141211T230000Z${TAB}20141212T230000Z" "out11.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-recurring-day.txt" 2>> $ERROR -cp "$FBFILE" out12.tmp - ! grep -q '^2' "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out12.tmp + + ! grep -q '^2' "out12.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation.sh --- a/tests/test_person_invitation.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_invitation.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,8 +4,6 @@ USER="mailto:vincent.vole@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -37,12 +35,17 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out2fo.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -50,7 +53,10 @@ | tee out3.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -62,11 +68,17 @@ && echo "Success" \ || echo "Failed" - ! grep -q "event7@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4f.tmp + + ! grep -q "event7@example.com" "out4f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "event7@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out4fo.tmp + + grep -q "event7@example.com" "out4fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -74,8 +86,11 @@ | tee out5.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "event6@example.com" "$FBFILE" \ -&& ! grep -q "event7@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5f.tmp + + grep -q "event6@example.com" "out5f.tmp" \ +&& ! grep -q "event7@example.com" "out5f.tmp" \ && echo "Success" \ || echo "Failed" @@ -87,11 +102,17 @@ && echo "Success" \ || echo "Failed" - ! grep -q "event6@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out6f.tmp + + ! grep -q "event6@example.com" "out6f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "event6@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out6fo.tmp + + grep -q "event6@example.com" "out6fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -103,11 +124,17 @@ && echo "Success" \ || echo "Failed" - ! grep -q "event6@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out7f.tmp + + ! grep -q "event6@example.com" "out7f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "event6@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out7fo.tmp + + ! grep -q "event6@example.com" "out7fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -119,10 +146,16 @@ && echo "Success" \ || echo "Failed" - ! grep -q "spoof2@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out8f.tmp + + ! grep -q "spoof2@example.com" "out8f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "spoof2@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out8fo.tmp + + ! grep -q "spoof2@example.com" "out8fo.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation_add.sh --- a/tests/test_person_invitation_add.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_invitation_add.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,10 +4,6 @@ USER="mailto:vincent.vole@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -21,7 +17,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out1f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -35,12 +34,17 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out2fo.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out2fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -54,7 +58,10 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -64,7 +71,10 @@ | "$SHOWMAIL" \ > out4.tmp - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDEROTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +> out4fo.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out4fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -72,8 +82,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-add-person-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out4f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out4f.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" @@ -87,13 +100,19 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ -&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out5f.tmp" \ +&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ -&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out5fo.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out5fo.tmp" \ +&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out5fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -118,13 +137,19 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ -&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out7f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out7f.tmp" \ +&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out7f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out7fo.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out7fo.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out7fo.tmp" \ && echo "Success" \ || echo "Failed" @@ -146,7 +171,10 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out9f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out9f.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out9f.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation_counter.sh --- a/tests/test_person_invitation_counter.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_invitation_counter.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,11 +4,6 @@ USER="mailto:vincent.vole@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOFFERFILE="$STORE/$USER/freebusy-offers" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" -FBSENDERREQUESTS="$STORE/$SENDER/requests" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -28,7 +23,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out0f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out0f.tmp" \ && echo "Success" \ || echo "Failed" @@ -42,8 +40,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "event6@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + ! grep -q "event6@example.com" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -61,9 +61,11 @@ # Note that the invitation has only been prepared, not processed. - ! [ -e "$FBFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ - && ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" @@ -72,8 +74,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBOFFERFILE" ] \ -|| ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out2o.tmp + + ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out2o.tmp" \ && echo "Success" \ || echo "Failed" @@ -81,13 +85,18 @@ "$OUTGOING_SCRIPT" $ARGS < out2r.tmp 2>> $ERROR - ! [ -e "$FBFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ - && ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f2.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f2.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out2f2.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out2o2.tmp + + grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out2o2.tmp" \ && echo "Success" \ || echo "Failed" @@ -98,21 +107,32 @@ | "$SHOWMAIL" \ > out3.tmp - ! [ -e "$FBSENDEROTHERFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDEROTHERFILE" \ - && ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBSENDEROTHERFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +> out3f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3f.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T160000' "$STORE/$SENDER/objects/event6@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event6@example.com" \ +> out3O.tmp + + grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T160000' "out3O.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T170000' "$STORE/$SENDER/counters/objects/event6@example.com/$USER" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event6@example.com" "$USER" \ +> out3C.tmp + + grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T170000' "out3C.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'event6@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +> out3R.tmp + + grep -q 'event6@example.com' "out3R.tmp" \ && echo "Success" \ || echo "Failed" @@ -137,11 +157,17 @@ "$OUTGOING_SCRIPT" $ARGS < out5.tmp 2>> $ERROR - ! [ -e "$STORE/$SENDER/counters/objects/event6@example.com/$USER" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event6@example.com" "$USER" \ +> out5C.tmp + + ! grep -q 'event6@example.com' "out5C.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q 'event6@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "requests" \ +> out5R.tmp + + ! grep -q 'event6@example.com' "out5R.tmp" \ && echo "Success" \ || echo "Failed" @@ -149,13 +175,18 @@ | "$SHOWMAIL" \ > out6.tmp - ! [ -e "$FBFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ - && ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out6f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out6f.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out6f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out6o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out6o.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out6o.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation_decline_instance.sh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test_person_invitation_decline_instance.sh Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,392 @@ +#!/bin/sh + +. "`dirname \"$0\"`/common.sh" + +USER1="mailto:vincent.vole@example.com" +USER2="mailto:harvey.horse@example.com" +SENDER="mailto:paul.boddie@example.com" + +mkdir -p "$PREFS/$USER1" +echo 'Europe/Oslo' > "$PREFS/$USER1/TZID" +echo 'share' > "$PREFS/$USER1/freebusy_sharing" + +mkdir -p "$PREFS/$USER2" +echo 'Europe/Oslo' > "$PREFS/$USER2/TZID" +echo 'share' > "$PREFS/$USER2/freebusy_sharing" + +mkdir -p "$PREFS/$SENDER" +echo 'Europe/Oslo' > "$PREFS/$SENDER/TZID" + +# Test free/busy responses. + + "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/fb-request-person-all.txt" 2>> $ERROR \ +| "$SHOWMAIL" \ +> out0.tmp + + grep -q 'METHOD:REPLY' out0.tmp \ +&& ! grep -q '^FREEBUSY' out0.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/fb-request-person.txt" 2>> $ERROR \ +| "$SHOWMAIL" \ +> out1.tmp + + grep -q 'METHOD:REPLY' out1.tmp \ +&& ! grep -q '^FREEBUSY' out1.tmp \ +&& echo "Success" \ +|| echo "Failed" + +# Publish an event, testing registration in the outgoing handler. + +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-rdate.txt" 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out1f.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out1f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should be an event created by the sender. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event26@example.com" \ +> out1O.tmp + + grep -q 'event26@example.com' "out1O.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test registration in the incoming handler for the recipients. + + "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-rdate.txt" 2>> $ERROR \ +| "$SHOWMAIL" \ +> out2.tmp + + ! grep -q 'METHOD:REPLY' out2.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out2f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy_other" "$SENDER" \ +> out2o.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out2o.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out2f2.tmp + + ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out2f2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy_other" "$SENDER" \ +> out2o2.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out2o2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should be an event created by the sender. + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "object" "event26@example.com" \ +> out2O.tmp + + grep -q 'event26@example.com' "out2O.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "object" "event26@example.com" \ +> out2O2.tmp + + grep -q 'event26@example.com' "out2O2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test acceptance and registration in the outgoing handler. + + "$ACCEPT_SCRIPT" $ACCEPT_ARGS "$USER1" "event26@example.com" 2>> $ERROR \ +| tee out3.tmp \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out3f.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out3f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$ACCEPT_SCRIPT" $ACCEPT_ARGS "$USER2" "event26@example.com" 2>> $ERROR \ +| tee out32.tmp \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out3f2.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out3f2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test registration in the incoming handler. + + "$PERSON_SCRIPT" $ARGS < out3.tmp 2>> $ERROR \ +| "$SHOWMAIL" \ +> out4.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out4f.tmp + + [ `grep "event26@example.com" "out4f.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out4f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy_other" "$SENDER" \ +> out4o.tmp + + [ `grep "event26@example.com" "out4o.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out4o.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$PERSON_SCRIPT" $ARGS < out32.tmp 2>> $ERROR \ +| "$SHOWMAIL" \ +> out42.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out4f2.tmp + + [ `grep "event26@example.com" "out4f2.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out4f2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy_other" "$SENDER" \ +> out4o2.tmp + + [ `grep "event26@example.com" "out4o2.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out4o2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test recurrence declining in the outgoing handler. +# Only the first user declines. + + "$DECLINE_SCRIPT" $DECLINE_ARGS "$USER1" "20141011T100000" "20141011T110000" "event26@example.com" "20141011T100000" 2>> $ERROR \ +| tee out5.tmp \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out5s.tmp + + [ `grep "event26@example.com" "out5s.tmp" | wc -l` = '1' ] \ +&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out5s.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should be a recurrence created by the user. + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "object" "event26@example.com" \ +> out5O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "recurrence" "event26@example.com" "20141011T100000" \ +> out5R.tmp + + grep -q 'event26@example.com' "out5O.tmp" \ +&& grep -q 'event26@example.com' "out5R.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test declining in the incoming handler. + + "$PERSON_SCRIPT" $ARGS < out5.tmp 2>> $ERROR \ +| tee out6r.tmp \ +| "$SHOWMAIL" \ +> out6.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out6f.tmp + + [ `grep "event26@example.com" "out6f.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER1" \ +> out6o.tmp + + [ `grep "event26@example.com" "out6o.tmp" | wc -l` = '1' ] \ +&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6o.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# The second user is still attending the original event. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER2" \ +> out6o2.tmp + + [ `grep "event26@example.com" "out6o2.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6o2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should be a recurrence created by the user. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event26@example.com" \ +> out6O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "recurrence" "event26@example.com" "20141011T100000" \ +> out6R.tmp + + grep -q 'event26@example.com' "out6O.tmp" \ +&& grep -q 'event26@example.com' "out6R.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# This should cause the organiser to tell the second user about the recurrence. + + grep -q 'METHOD:REQUEST' out6.tmp \ +&& echo "Success" \ +|| echo "Failed" + + "$PERSON_SCRIPT" $ARGS < out6r.tmp 2>> $ERROR \ +| "$SHOWMAIL" \ +> out62.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "object" "event26@example.com" \ +> out6O2.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "recurrence" "event26@example.com" "20141011T100000" \ +> out6R2.tmp + +# The second user's schedule should remain unchanged. +# NOTE: The nature of the periods might need to change, with the recurrence +# NOTE: taking over the affected period. + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out6f2.tmp + + [ `grep "event26@example.com" "out6f2.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6f2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test recurrence acceptance in the outgoing handler. + + "$ACCEPT_SCRIPT" $ACCEPT_ARGS "$USER1" "20141011T100000" "20141011T110000" "event26@example.com" "20141011T100000" 2>> $ERROR \ +| tee out7.tmp \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out7s.tmp + + [ `grep "event26@example.com" "out7s.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out7s.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should still be a recurrence created by the user. + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "object" "event26@example.com" \ +> out7O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "recurrence" "event26@example.com" "20141011T100000" \ +> out7R.tmp + + grep -q 'event26@example.com' "out7O.tmp" \ +&& grep -q 'event26@example.com' "out7R.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test acceptance in the incoming handler. + + "$PERSON_SCRIPT" $ARGS < out7.tmp 2>> $ERROR \ +| "$SHOWMAIL" \ +> out8.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out8f.tmp + + [ `grep "event26@example.com" "out8f.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out8f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER1" \ +> out8o.tmp + + [ `grep "event26@example.com" "out8o.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out8o.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# The second user should not have been affected. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER2" \ +> out8o2.tmp + + [ `grep "event26@example.com" "out8o2.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out8o2.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# There should be a recurrence created by the user. + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event26@example.com" \ +> out8O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "recurrence" "event26@example.com" "20141011T100000" \ +> out8R.tmp + + grep -q 'event26@example.com' "out8O.tmp" \ +&& grep -q 'event26@example.com' "out8R.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test recurrence declining in the outgoing handler. +# Now the second user declines the parent event. + + "$DECLINE_SCRIPT" $DECLINE_ARGS "$USER2" "event26@example.com" 2>> $ERROR \ +| tee out9.tmp \ +| "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out9s.tmp + + [ `grep "event26@example.com" "out9s.tmp" | wc -l` = '0' ] \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out9s.tmp" \ +&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out9s.tmp" \ +&& echo "Success" \ +|| echo "Failed" + +# Test declining in the incoming handler. + + "$PERSON_SCRIPT" $ARGS < out9.tmp 2>> $ERROR \ +| "$SHOWMAIL" \ +> out10.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out10f.tmp + + [ `grep "event26@example.com" "out10f.tmp" | wc -l` = '2' ] \ +&& grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out10f.tmp" \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out10f.tmp" \ +&& echo "Success" \ +|| echo "Failed" + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER2" \ +> out10o.tmp + + [ `grep "event26@example.com" "out10o.tmp" | wc -l` = '1' ] \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out10o.tmp" \ +&& grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out10o.tmp" \ +&& echo "Success" \ +|| echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation_recurring.sh --- a/tests/test_person_invitation_recurring.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_invitation_recurring.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,16 +4,13 @@ USER="mailto:vincent.vole@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" -FBSENDERFILE="$STORE/$SENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" echo 'share' > "$PREFS/$USER/freebusy_sharing" mkdir -p "$PREFS/$SENDER" -echo 'Europe/Oslo' > "$PREFS/$USER/TZID" +echo 'Europe/Oslo' > "$PREFS/$SENDER/TZID" # Test free/busy responses. @@ -39,7 +36,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out1f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -53,12 +53,17 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out2o.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out2o.tmp" \ && echo "Success" \ || echo "Failed" @@ -68,7 +73,10 @@ | tee out3.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -76,8 +84,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring-instance.txt" 2>> $ERROR - [ `grep "event8@example.com" "$FBSENDERFILE" | wc -l` = '2' ] \ -&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out3s.tmp + + [ `grep "event8@example.com" "out3s.tmp" | wc -l` = '2' ] \ +&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "out3s.tmp" \ && echo "Success" \ || echo "Failed" @@ -91,13 +102,19 @@ && echo "Success" \ || echo "Failed" - [ `grep "event8@example.com" "$FBFILE" | wc -l` = '2' ] \ -&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4f.tmp + + [ `grep "event8@example.com" "out4f.tmp" | wc -l` = '2' ] \ +&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" - [ `grep "event8@example.com" "$FBOTHERFILE" | wc -l` = '2' ] \ -&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out4o.tmp + + [ `grep "event8@example.com" "out4o.tmp" | wc -l` = '2' ] \ +&& ! grep -q "^20141114T090000Z${TAB}20141114T100000Z" "out4o.tmp" \ && echo "Success" \ || echo "Failed" @@ -105,8 +122,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-reschedule-instance.txt" 2>> $ERROR - grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out4s.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out4s.tmp" \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out4s.tmp" \ && echo "Success" \ || echo "Failed" @@ -120,13 +140,19 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBFILE" \ -&& grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5f.tmp + + ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out5f.tmp" \ +&& grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBOTHERFILE" \ -&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out5o.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out5o.tmp" \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out5o.tmp" \ && echo "Success" \ || echo "Failed" @@ -136,8 +162,11 @@ | tee out6.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBFILE" \ -&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out6f.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6f.tmp" \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out6f.tmp" \ && echo "Success" \ || echo "Failed" @@ -145,7 +174,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring.txt" 2>> $ERROR - ! grep -q "event8@example.com" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out6s.tmp + + ! grep -q "event8@example.com" "out6s.tmp" \ && echo "Success" \ || echo "Failed" @@ -153,17 +185,23 @@ "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring.txt" 2>> $ERROR \ | "$SHOWMAIL" \ -> out6.tmp +> out7.tmp - ! grep -q 'METHOD:REPLY' out6.tmp \ + ! grep -q 'METHOD:REPLY' out7.tmp \ && echo "Success" \ || echo "Failed" - ! grep -q "event8@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out7f.tmp + + ! grep -q "event8@example.com" "out7f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "event8@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out7o.tmp + + ! grep -q "event8@example.com" "out7o.tmp" \ && echo "Success" \ || echo "Failed" @@ -173,53 +211,74 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out7s.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out7s.tmp" \ && echo "Success" \ || echo "Failed" "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring.txt" 2>> $ERROR \ | "$SHOWMAIL" \ -> out7.tmp +> out8.tmp - ! grep -q 'METHOD:REPLY' out7.tmp \ + ! grep -q 'METHOD:REPLY' out8.tmp \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out8f.tmp + + ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out8f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out8o.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out8o.tmp" \ && echo "Success" \ || echo "Failed" "$ACCEPT_SCRIPT" $ACCEPT_ARGS "$USER" "event8@example.com" 2>> $ERROR \ -| tee out8.tmp \ +| tee out9.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out9f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out9f.tmp" \ && echo "Success" \ || echo "Failed" "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring.txt" 2>> $ERROR - ! grep -q "event8@example.com" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out9s.tmp + + ! grep -q "event8@example.com" "out9s.tmp" \ && echo "Success" \ || echo "Failed" "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring.txt" 2>> $ERROR \ | "$SHOWMAIL" \ -> out9.tmp +> out10.tmp - ! grep -q 'METHOD:REPLY' out9.tmp \ + ! grep -q 'METHOD:REPLY' out10.tmp \ && echo "Success" \ || echo "Failed" - ! grep -q "event8@example.com" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out10f.tmp + + ! grep -q "event8@example.com" "out10f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "event8@example.com" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out10o.tmp + + ! grep -q "event8@example.com" "out10o.tmp" \ && echo "Success" \ || echo "Failed" @@ -228,32 +287,44 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-day-floating.txt" 2>> $ERROR - grep -q "^20141211T230000Z${TAB}20141212T230000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out10s.tmp + + grep -q "^20141211T230000Z${TAB}20141212T230000Z" "out10s.tmp" \ && echo "Success" \ || echo "Failed" "$PERSON_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-day-floating.txt" 2>> $ERROR \ | "$SHOWMAIL" \ -> out10.tmp +> out11.tmp - ! grep -q 'METHOD:REPLY' out10.tmp \ + ! grep -q 'METHOD:REPLY' out11.tmp \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141211T220000Z${TAB}20141212T220000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out11f.tmp + + ! grep -q "^20141211T220000Z${TAB}20141212T220000Z" "out11f.tmp" \ && echo "Success" \ || echo "Failed" # (The organiser is not attending.) - ! grep -q "^20141211T220000Z${TAB}20141212T220000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out11o.tmp + + ! grep -q "^20141211T220000Z${TAB}20141212T220000Z" "out11o.tmp" \ && echo "Success" \ || echo "Failed" "$ACCEPT_SCRIPT" $ACCEPT_ARGS "$USER" "event12@example.com" 2>> $ERROR \ -| tee out11.tmp \ +| tee out12.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141211T220000Z${TAB}20141212T220000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out12f.tmp + + grep -q "^20141211T220000Z${TAB}20141212T220000Z" "out12f.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_invitation_refresh.sh --- a/tests/test_person_invitation_refresh.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_invitation_refresh.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,9 +4,6 @@ USER="mailto:vincent.vole@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" -FBSENDERFILE="$STORE/$SENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -21,7 +18,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out1f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -45,12 +45,17 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + ! grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out3f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -60,7 +65,10 @@ | tee out4.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" @@ -78,8 +86,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-person-recurring-reschedule-instance.txt" 2>> $ERROR - grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out5f.tmp + + grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out5f.tmp" \ +&& ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" @@ -102,8 +113,14 @@ | "$SHOWMAIL" \ > out6a.tmp - [ -e "$STORE/$USER/objects/event8@example.com" ] \ -&& [ -e "$STORE/$USER/recurrences/event8@example.com/20141010T080000Z" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event8@example.com" \ +> out6O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "recurrence" "event8@example.com" "20141010T080000Z" \ +> out6R.tmp + + grep -q 'event8@example.com' "out6O.tmp" \ +&& grep -q 'event8@example.com' "out6R.tmp" \ && echo "Success" \ || echo "Failed" @@ -112,14 +129,26 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-person-recurring-rescheduled-instance.txt" 2>> $ERROR - [ -e "$STORE/$SENDER/objects/event8@example.com" ] \ -&& ! [ -e "$STORE/$SENDER/recurrences/event8@example.com/20141010T080000Z" ] \ -&& [ -e "$STORE/$SENDER/cancellations/recurrences/event8@example.com/20141010T080000Z" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event8@example.com" \ +> out6O2.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "recurrence" "event8@example.com" "20141010T080000Z" \ +> out6R2.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "cancelled_recurrences" "event8@example.com" \ +> out6C.tmp + + grep -q 'event8@example.com' "out6O2.tmp" \ +&& ! grep -q 'event8@example.com' "out6R2.tmp" \ +&& grep -q '20141010T080000Z' "out6C.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out6f.tmp + + ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out6f.tmp" \ +&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out6f.tmp" \ && echo "Success" \ || echo "Failed" @@ -143,9 +172,18 @@ | "$SHOWMAIL" \ > out7a.tmp - [ -e "$STORE/$USER/objects/event8@example.com" ] \ -&& ! [ -e "$STORE/$USER/recurrences/event8@example.com/20141010T080000Z" ] \ -&& [ -e "$STORE/$USER/cancellations/recurrences/event8@example.com/20141010T080000Z" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "object" "event8@example.com" \ +> out7O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER" "recurrence" "event8@example.com" "20141010T080000Z" \ +> out7R.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER" "cancelled_recurrences" "event8@example.com" \ +> out7C.tmp + + grep -q 'event8@example.com' "out7O.tmp" \ +&& ! grep -q 'event8@example.com' "out7R.tmp" \ +&& grep -q '20141010T080000Z' "out7C.tmp" \ && echo "Success" \ || echo "Failed" @@ -153,8 +191,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-add-person-recurring-rescheduled-instance.txt" 2>> $ERROR - grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out7f.tmp + + grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out7f.tmp" \ +&& ! grep -q "^20141011T080000Z${TAB}20141011T090000Z" "out7f.tmp" \ && echo "Success" \ || echo "Failed" @@ -162,17 +203,32 @@ | "$SHOWMAIL" \ > out8.tmp - [ -e "$STORE/$USER/objects/event8@example.com" ] \ -&& [ -e "$STORE/$USER/recurrences/event8@example.com/20141010T080000Z" ] \ -&& ! [ -e "$STORE/$USER/cancellations/recurrences/event8@example.com/20141010T080000Z" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "object" "event8@example.com" \ +> out8O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER" "recurrence" "event8@example.com" "20141010T080000Z" \ +> out8R.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER" "cancelled_recurrences" "event8@example.com" \ +> out8C.tmp + + grep -q 'event8@example.com' "out8O.tmp" \ +&& grep -q 'event8@example.com' "out8R.tmp" \ +&& ! grep -q '20141010T080000Z' "out8C.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out8f.tmp + + ! grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out8f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out8f.tmp + + grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out8f.tmp" \ && echo "Success" \ || echo "Failed" @@ -182,7 +238,10 @@ | tee out9.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141010T080000Z${TAB}20141010T090000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out9f.tmp + + grep -q "^20141010T080000Z${TAB}20141010T090000Z" "out9f.tmp" \ && echo "Success" \ || echo "Failed" @@ -205,7 +264,13 @@ | "$SHOWMAIL" \ > out11.tmp - [ -e "$STORE/$USER/objects/event8@example.com" ] \ -&& [ -e "$STORE/$USER/recurrences/event8@example.com/20141010T080000Z" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "object" "event8@example.com" \ +> out11O.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER" "recurrence" "event8@example.com" "20141010T080000Z" \ +> out11R.tmp + + grep -q 'event8@example.com' "out11O.tmp" \ +&& grep -q 'event8@example.com' "out11R.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_person_non_participation.sh --- a/tests/test_person_non_participation.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_person_non_participation.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,9 +5,6 @@ USER="mailto:vincent.vole@example.com" IMPOSTER="mailto:oliver.otter@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" -FBIMPOSTERFILE="$STORE/$SENDER/freebusy-other/$IMPOSTER" mkdir -p "$PREFS/$USER" echo 'no' > "$PREFS/$USER/participating" @@ -27,7 +24,10 @@ "$OUTGOING_SCRIPT" < "$TEMPLATES/event-request-person.txt" $ARGS 2>> $ERROR - [ -e "$STORE/$SENDER/objects/event6@example.com" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event6@example.com" \ +> out0O.tmp + + grep -q "event6@example.com" "out0O.tmp" \ && echo "Success" \ || echo "Failed" @@ -41,11 +41,17 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "event6@example.com" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - ! [ -e "$FBOTHERFILE" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out2s.tmp + + ! grep -q "event6@example.com" "out2s.tmp" \ && echo "Success" \ || echo "Failed" @@ -55,8 +61,10 @@ | tee out3.tmp \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" @@ -68,12 +76,16 @@ | "$SHOWMAIL" \ > out5.tmp - [ -e "$STORE/$SENDER/objects/event6@example.com" ] \ -&& ! grep -q "otter" "$STORE/$SENDER/objects/event6@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "object" "event6@example.com" \ +> out5O.tmp + + ! grep -q "otter" "out5O.tmp" \ && echo "Success" \ || echo "Failed" - ( ! [ -e "$FBIMPOSTERFILE" ] \ - || ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBIMPOSTERFILE") \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$IMPOSTER" \ +> out5s.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out5s.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_add.sh --- a/tests/test_resource_invitation_add.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_add.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,9 +4,6 @@ USER="mailto:resource-room-confroom@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -20,7 +17,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out0f.tmp \ +| grep -q "^20141212T090000Z${TAB}20141212T100000Z" \ && echo "Success" \ || echo "Failed" @@ -34,7 +33,9 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out1f.tmp \ +| grep -q "^20141212T090000Z${TAB}20141212T100000Z" \ && echo "Success" \ || echo "Failed" @@ -44,7 +45,9 @@ | "$SHOWMAIL" \ > out2.tmp - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDEROTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +| tee out2o.tmp \ +| grep -q "^20141212T090000Z${TAB}20141212T100000Z" \ && echo "Success" \ || echo "Failed" @@ -52,8 +55,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-add-recurring.txt" 2>> $ERROR - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDERFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out2f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out2f.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" @@ -67,8 +73,11 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ -&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out3f.tmp" \ +&& ! grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -94,8 +103,11 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5f.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out5f.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" @@ -105,7 +117,10 @@ | "$SHOWMAIL" \ > out6.tmp - grep -q "^20141212T090000Z${TAB}20141212T100000Z" "$FBSENDEROTHERFILE" \ -&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "$FBSENDEROTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +> out6o.tmp + + grep -q "^20141212T090000Z${TAB}20141212T100000Z" "out6o.tmp" \ +&& grep -q "^20150109T090000Z${TAB}20150109T100000Z" "out6o.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints.sh --- a/tests/test_resource_invitation_constraints.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,12 +5,6 @@ USER="mailto:resource-room-sauna@example.com" SENDER="mailto:paul.boddie@example.com" RIVALSENDER="mailto:vincent.vole@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOFFERFILE="$STORE/$USER/freebusy-offers" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" -FBSENDERREQUESTS="$STORE/$SENDER/requests" -FBRIVALSENDERFILE="$STORE/$RIVALSENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -32,7 +26,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-bad.txt" 2>> $ERROR - grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out0f.tmp \ +| grep -q "^20141126T151000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -48,12 +44,16 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +| tee out1o.tmp \ +| grep -q "^20141126T151500Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -64,20 +64,28 @@ | "$SHOWMAIL" \ > out2.tmp - ( ! [ -e "$FBSENDEROTHERFILE" ] \ - || ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDEROTHERFILE") \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +> out2f.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' "$STORE/$SENDER/objects/event13@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event13@example.com" \ +| tee out2O.tmp \ +| grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' "$STORE/$SENDER/counters/objects/event13@example.com/$USER" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event13@example.com" "$USER" \ +| tee out2C.tmp \ +| grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' \ && echo "Success" \ || echo "Failed" - grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +| tee out2r.tmp \ +| grep -q 'event13@example.com' \ && echo "Success" \ || echo "Failed" @@ -85,8 +93,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-rival.txt" 2>> $ERROR - ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ -&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "freebusy" \ +> out2R.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out2R.tmp" \ +&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out2R.tmp" \ && echo "Success" \ || echo "Failed" @@ -108,12 +119,17 @@ | "$SHOWMAIL" \ > out4.tmp - ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ -&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "freebusy" \ +> out4R.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out4R.tmp" \ +&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out4R.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' "$STORE/$RIVALSENDER/objects/event18@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "object" "event18@example.com" \ +| tee out4O.tmp \ +| grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' \ && echo "Success" \ || echo "Failed" @@ -134,9 +150,12 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-good.txt" 2>> $ERROR - ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out5f.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out5f.tmp" \ +&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out5f.tmp" \ +&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" @@ -144,7 +163,10 @@ && echo "Success" \ || echo "Failed" - ! grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +> out5r.tmp + + ! grep -q 'event13@example.com' "out5r.tmp" \ && echo "Success" \ || echo "Failed" @@ -159,13 +181,18 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out6f.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out6o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out6o.tmp" \ && echo "Success" \ || echo "Failed" @@ -176,10 +203,13 @@ | "$SHOWMAIL" \ > out7.tmp - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out7f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out7f.tmp" \ +&& ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out7f.tmp" \ +&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out7f.tmp" \ +&& ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out7f.tmp" \ && echo "Success" \ || echo "Failed" @@ -187,7 +217,10 @@ && echo "Success" \ || echo "Failed" - ! grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +> out7r.tmp + + ! grep -q 'event13@example.com' "out7r.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_alternative.sh --- a/tests/test_resource_invitation_constraints_alternative.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_alternative.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,12 +5,6 @@ USER="mailto:resource-room-sauna@example.com" SENDER="mailto:paul.boddie@example.com" RIVALSENDER="mailto:vincent.vole@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOFFERFILE="$STORE/$USER/freebusy-offers" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" -FBSENDERREQUESTS="$STORE/$SENDER/requests" -FBRIVALSENDERFILE="$STORE/$RIVALSENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -32,7 +26,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-bad.txt" 2>> $ERROR - grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out0f.tmp \ +| grep -q "^20141126T151000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -47,12 +43,16 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +| tee out1o.tmp \ +| grep -q "^20141126T151500Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -63,21 +63,29 @@ | "$SHOWMAIL" \ > out2.tmp - [ ! -e "$FBSENDEROTHERFILE" ] \ -|| ( ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBSENDEROTHERFILE" \ - && ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBSENDEROTHERFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy_other" "$USER" \ +> out2f.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out2f.tmp" \ +&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' "$STORE/$SENDER/objects/event13@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "object" "event13@example.com" \ +| tee out2O.tmp \ +| grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161000' \ && echo "Success" \ || echo "Failed" - grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' "$STORE/$SENDER/counters/objects/event13@example.com/$USER" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event13@example.com" "$USER" \ +| tee out2C.tmp \ +| grep -q 'DTSTART;TZID=Europe/Oslo.*:20141126T161500' \ && echo "Success" \ || echo "Failed" - grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +| tee out2R.tmp \ +| grep -q 'event13@example.com' \ && echo "Success" \ || echo "Failed" @@ -94,12 +102,17 @@ | "$SHOWMAIL" \ > out4.tmp - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4f.tmp + + ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out4o.tmp + + ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out4o.tmp" \ && echo "Success" \ || echo "Failed" @@ -107,8 +120,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-rival.txt" 2>> $ERROR - ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ -&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "freebusy" \ +> out4r.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out4r.tmp" \ +&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out4r.tmp" \ && echo "Success" \ || echo "Failed" @@ -123,11 +139,16 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out5f.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out5o.tmp + + ! grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out5o.tmp" \ && echo "Success" \ || echo "Failed" @@ -137,13 +158,18 @@ | "$SHOWMAIL" \ > out6.tmp - ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ -&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ -&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "$FBRIVALSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "freebusy" \ +> out6r.tmp + + ! grep -q "^20141126T151000Z${TAB}20141126T154500Z" "out6r.tmp" \ +&& ! grep -q "^20141126T151500Z${TAB}20141126T154500Z" "out6r.tmp" \ +&& grep -q "^20141126T153000Z${TAB}20141126T154500Z" "out6r.tmp" \ && echo "Success" \ || echo "Failed" - grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' "$STORE/$RIVALSENDER/objects/event18@example.com" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "object" "event18@example.com" \ +| tee out6O.tmp \ +| grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_multiple.sh --- a/tests/test_resource_invitation_constraints_multiple.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_multiple.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,9 +5,6 @@ USER="mailto:resource-room-sauna@example.com" SENDER="mailto:paul.boddie@example.com" OUTSIDESENDER="mailto:paul.boddie@example.net" -FBFILE="$STORE/$USER/freebusy" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBOUTSIDESENDERFILE="$STORE/$OUTSIDESENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -32,7 +29,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-outsider.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOUTSIDESENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$OUTSIDESENDER" "freebusy" \ +| tee out0f.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -48,8 +47,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -71,8 +72,9 @@ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE" ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out2f.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -105,7 +107,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR - grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out3f.tmp \ +| grep -q "^20141126T160000Z${TAB}20141126T164500Z" \ && echo "Success" \ || echo "Failed" @@ -121,8 +125,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out4f.tmp + + ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" @@ -136,7 +142,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR - grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out4s.tmp \ +| grep -q "^20141126T160000Z${TAB}20141126T164500Z" \ && echo "Success" \ || echo "Failed" @@ -152,8 +160,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out5f.tmp + + ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out5f.tmp" \ && echo "Success" \ || echo "Failed" @@ -168,7 +178,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-acl.txt" 2>> $ERROR - grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out5s.tmp \ +| grep -q "^20141126T160000Z${TAB}20141126T164500Z" \ && echo "Success" \ || echo "Failed" @@ -184,8 +196,10 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out6f.tmp + + ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out6f.tmp" \ && echo "Success" \ || echo "Failed" @@ -214,8 +228,9 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +| tee out7f.tmp \ +| grep -q "^20141126T160000Z${TAB}20141126T164500Z" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_next_free.sh --- a/tests/test_resource_invitation_constraints_next_free.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_next_free.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,13 +5,6 @@ USER="mailto:resource-room-sauna@example.com" SENDER="mailto:paul.boddie@example.com" RIVALSENDER="mailto:vincent.vole@example.com" -FBFILE="$STORE/$USER/freebusy" -FBOTHERFILE="$STORE/$USER/freebusy-other/$SENDER" -FBOFFERFILE="$STORE/$USER/freebusy-offers" -FBSENDERFILE="$STORE/$SENDER/freebusy" -FBSENDEROTHERFILE="$STORE/$SENDER/freebusy-other/$USER" -FBSENDERREQUESTS="$STORE/$SENDER/requests" -FBRIVALSENDERFILE="$STORE/$RIVALSENDER/freebusy" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -32,8 +25,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-busy.txt" 2>> $ERROR - [ `grep "event19@example.com" "$FBRIVALSENDERFILE" | wc -l` = '5' ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBRIVALSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$RIVALSENDER" "freebusy" \ +> out0s.tmp + + [ `grep "event19@example.com" "out0s.tmp" | wc -l` = '5' ] \ +&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out0s.tmp" \ && echo "Success" \ || echo "Failed" @@ -49,8 +45,11 @@ && echo "Success" \ || echo "Failed" - [ `grep "event19@example.com" "$FBFILE" | wc -l` = '5' ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + [ `grep "event19@example.com" "out1f.tmp" | wc -l` = '5' ] \ +&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" @@ -67,8 +66,11 @@ | "$SHOWMAIL" \ > out3.tmp - grep -q "^20141126T160000Z${TAB}20141126T170000Z" "$FBOTHERFILE" \ -&& grep -q "^20141126T180000Z${TAB}20141126T190000Z" "$FBOTHERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_other" "$SENDER" \ +> out3f.tmp + + grep -q "^20141126T160000Z${TAB}20141126T170000Z" "out3f.tmp" \ +&& grep -q "^20141126T180000Z${TAB}20141126T190000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" @@ -76,7 +78,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-sauna-good.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out3s.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T154500Z" \ && echo "Success" \ || echo "Failed" @@ -94,12 +98,15 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "$FBOFFERFILE" \ -&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out6o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "out6o.tmp" \ +&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "out6o.tmp" \ +&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out6o.tmp" \ && echo "Success" \ || echo "Failed" @@ -110,16 +117,23 @@ | "$SHOWMAIL" \ > out7.tmp - grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out7s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out7s.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out7s.tmp" \ && echo "Success" \ || echo "Failed" - [ -e "$STORE/$SENDER/counters/objects/event13@example.com/$USER" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event13@example.com" "$USER" \ +| tee out7C.tmp \ +| grep -q "event13@example.com" \ && echo "Success" \ || echo "Failed" - grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +| tee out7R.tmp \ +| grep -q 'event13@example.com' \ && echo "Success" \ || echo "Failed" @@ -132,16 +146,25 @@ "$OUTGOING_SCRIPT" $ARGS < out8.tmp 2>> $ERROR - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBSENDERFILE" \ -&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out8s.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out8s.tmp" \ +&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out8s.tmp" \ && echo "Success" \ || echo "Failed" - ! [ -e "$STORE/$SENDER/counters/objects/event13@example.com/$USER" ] \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "countered_object" "event13@example.com" "$USER" \ +> out8C.tmp + + ! grep -q "event13@example.com" "out8C.tmp" \ && echo "Success" \ || echo "Failed" - ! grep -q 'event13@example.com' "$FBSENDERREQUESTS" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "requests" \ +> out8R.tmp + + ! grep -q 'event13@example.com' "out8R.tmp" \ && echo "Success" \ || echo "Failed" @@ -154,18 +177,24 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out9o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out9o.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out9o.tmp" \ +&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "out9o.tmp" \ +&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "out9o.tmp" \ +&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "out9o.tmp" \ +&& ! grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out9o.tmp" \ && echo "Success" \ || echo "Failed" - [ `grep "event19@example.com" "$FBFILE" | wc -l` = '5' ] \ -&& [ `grep "event13@example.com" "$FBFILE" | wc -l` = '1' ] \ -&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out9f.tmp + + [ `grep "event19@example.com" "out9f.tmp" | wc -l` = '5' ] \ +&& [ `grep "event13@example.com" "out9f.tmp" | wc -l` = '1' ] \ +&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out9f.tmp" \ && echo "Success" \ || echo "Failed" @@ -180,18 +209,24 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out10o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out10o.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out10o.tmp" \ +&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "out10o.tmp" \ +&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "out10o.tmp" \ +&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "out10o.tmp" \ +&& ! grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out10o.tmp" \ && echo "Success" \ || echo "Failed" - [ `grep "event19@example.com" "$FBFILE" | wc -l` = '5' ] \ -&& [ `grep "event13@example.com" "$FBFILE" | wc -l` = '1' ] \ -&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out10f.tmp + + [ `grep "event19@example.com" "out10f.tmp" | wc -l` = '5' ] \ +&& [ `grep "event13@example.com" "out10f.tmp" | wc -l` = '1' ] \ +&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out10f.tmp" \ && echo "Success" \ || echo "Failed" @@ -212,17 +247,23 @@ # Note that the duration is different now. - ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "$FBOFFERFILE" \ -&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "$FBOFFERFILE" \ -&& grep -q "^20141126T200000Z${TAB}20141126T203000Z" "$FBOFFERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_offers" \ +> out12o.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T154500Z" "out12o.tmp" \ +&& ! grep -q "^20141126T160000Z${TAB}20141126T164500Z" "out12o.tmp" \ +&& ! grep -q "^20141126T170000Z${TAB}20141126T174500Z" "out12o.tmp" \ +&& ! grep -q "^20141126T180000Z${TAB}20141126T184500Z" "out12o.tmp" \ +&& ! grep -q "^20141126T190000Z${TAB}20141126T194500Z" "out12o.tmp" \ +&& grep -q "^20141126T200000Z${TAB}20141126T203000Z" "out12o.tmp" \ && echo "Success" \ || echo "Failed" - [ `grep "event19@example.com" "$FBFILE" | wc -l` = '5' ] \ -&& [ `grep "event13@example.com" "$FBFILE" | wc -l` = '1' ] \ -&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out12f.tmp + + [ `grep "event19@example.com" "out12f.tmp" | wc -l` = '5' ] \ +&& [ `grep "event13@example.com" "out12f.tmp" | wc -l` = '1' ] \ +&& grep -q "^20141126T200000Z${TAB}20141126T204500Z" "out12f.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_quota.sh --- a/tests/test_resource_invitation_constraints_quota.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_quota.sh Fri Apr 22 16:22:58 2016 +0200 @@ -5,11 +5,7 @@ USER1="mailto:resource-car-porsche911@example.com" USER2="mailto:resource-car-fiat500@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE1="$STORE/$USER1/freebusy" -FBFILE2="$STORE/$USER2/freebusy" -FBSENDERFILE="$STORE/$SENDER/freebusy" QUOTA=cars -JOURNALFILE="$JOURNAL/$QUOTA/journal/$SENDER" mkdir -p "$PREFS/$USER1" echo 'Europe/Oslo' > "$PREFS/$USER1/TZID" @@ -27,8 +23,7 @@ check_quota $QUOTA EOF -mkdir -p "$JOURNAL/$QUOTA" -echo '* PT1H' > "$JOURNAL/$QUOTA/limits" +"$SET_QUOTA_LIMIT" "$QUOTA" '*' 'PT1H' $SET_QUOTA_LIMIT_ARGS "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-car.txt" 2>> $ERROR \ | "$SHOWMAIL" \ @@ -43,7 +38,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out0s.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ && echo "Success" \ || echo "Failed" @@ -59,15 +56,17 @@ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE1" ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +| tee out1f.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event21@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +| tee out1e.tmp \ +| grep -q "event21@example.com" \ && echo "Success" \ || echo "Failed" @@ -75,7 +74,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR - grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out1s.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T163000Z" \ && echo "Success" \ || echo "Failed" @@ -91,28 +92,34 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE2" ] \ -|| ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is not confirmed). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event21@example.com" "$JOURNALFILE" \ -&& ! grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out2e.tmp + + grep -q "event21@example.com" "out2e.tmp" \ +&& ! grep -q "event22@example.com" "out2e.tmp" \ && echo "Success" \ || echo "Failed" # Increase the quota. -echo '* PT2H' > "$JOURNAL/$QUOTA/limits" +"$SET_QUOTA_LIMIT" "$QUOTA" '*' 'PT2H' $SET_QUOTA_LIMIT_ARGS # Attempt to schedule the event again. "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR - grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out2s.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T163000Z" \ && echo "Success" \ || echo "Failed" @@ -128,16 +135,19 @@ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE2" ] \ -&& grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +| tee out3f.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T163000Z" \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event21@example.com" "$JOURNALFILE" \ -&& grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out3e.tmp + + grep -q "event21@example.com" "out3e.tmp" \ +&& grep -q "event22@example.com" "out3e.tmp" \ && echo "Success" \ || echo "Failed" @@ -145,7 +155,10 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-car.txt" 2>> $ERROR - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out3s.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3s.tmp" \ && echo "Success" \ || echo "Failed" @@ -160,15 +173,20 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out4f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out4f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is retracted). - [ -e "$JOURNALFILE" ] \ -&& ! grep -q "event21@example.com" "$JOURNALFILE" \ -&& grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out4e.tmp + + ! grep -q "event21@example.com" "out4e.tmp" \ +&& grep -q "event22@example.com" "out4e.tmp" \ && echo "Success" \ || echo "Failed" @@ -198,16 +216,19 @@ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE2" ] \ -&& grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +| tee out5f.tmp \ +| grep -q "^20141126T153000Z${TAB}20141126T163000Z" \ && echo "Success" \ || echo "Failed" # Check the quota (event is still confirmed). - [ -e "$JOURNALFILE" ] \ -&& ! grep -q "event21@example.com" "$JOURNALFILE" \ -&& grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out5e.tmp + + ! grep -q "event21@example.com" "out5e.tmp" \ +&& grep -q "event22@example.com" "out5e.tmp" \ && echo "Success" \ || echo "Failed" @@ -215,7 +236,9 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out5s.tmp \ +| grep -q "^20141126T150000Z${TAB}20141126T160000Z" \ && echo "Success" \ || echo "Failed" @@ -231,15 +254,20 @@ && echo "Success" \ || echo "Failed" - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out6f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out6f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is still retracted and not newly confirmed). - [ -e "$JOURNALFILE" ] \ -&& ! grep -q "event21@example.com" "$JOURNALFILE" \ -&& grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out6e.tmp + + ! grep -q "event21@example.com" "out6e.tmp" \ +&& grep -q "event22@example.com" "out6e.tmp" \ && echo "Success" \ || echo "Failed" @@ -247,8 +275,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-moved.txt" 2>> $ERROR - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ -&& grep -q "^20141126T143000Z${TAB}20141126T153000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out6s.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out6s.tmp" \ +&& grep -q "^20141126T143000Z${TAB}20141126T153000Z" "out6s.tmp" \ && echo "Success" \ || echo "Failed" @@ -264,27 +295,33 @@ && echo "Success" \ || echo "Failed" - grep -q "^20141126T143000Z${TAB}20141126T153000Z" "$FBFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +| tee out7f.tmp \ +| grep -q "^20141126T143000Z${TAB}20141126T153000Z" \ && echo "Success" \ || echo "Failed" # Check the quota (event is newly confirmed). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event21@example.com" "$JOURNALFILE" \ -&& grep -q "event22@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out7e.tmp + + grep -q "event21@example.com" "out7e.tmp" \ +&& grep -q "event22@example.com" "out7e.tmp" \ && echo "Success" \ || echo "Failed" # Increase the quota. -echo '* PT3H' > "$JOURNAL/$QUOTA/limits" +"$SET_QUOTA_LIMIT" "$QUOTA" '*' 'PT3H' $SET_QUOTA_LIMIT_ARGS # Attempt to schedule an event involving both resources. "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-cars.txt" 2>> $ERROR - grep -q "^20141127T150000Z${TAB}20141127T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +| tee out7s.tmp \ +| grep -q "^20141127T150000Z${TAB}20141127T160000Z" \ && echo "Success" \ || echo "Failed" @@ -311,16 +348,23 @@ && echo "Success" \ || echo "Failed" - ( grep -q "^20141127T150000Z${TAB}20141127T160000Z" "$FBFILE1" \ - && ! grep -q "^20141127T150000Z${TAB}20141127T160000Z" "$FBFILE2" ) \ -|| ( ! grep -q "^20141127T150000Z${TAB}20141127T160000Z" "$FBFILE1" \ - && grep -q "^20141127T150000Z${TAB}20141127T160000Z" "$FBFILE2" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out8f.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out8f2.tmp + + ( grep -q "^20141127T150000Z${TAB}20141127T160000Z" "out8f.tmp" \ + && ! grep -q "^20141127T150000Z${TAB}20141127T160000Z" "out8f2.tmp" ) \ +|| ( ! grep -q "^20141127T150000Z${TAB}20141127T160000Z" "out8f.tmp" \ + && grep -q "^20141127T150000Z${TAB}20141127T160000Z" "out8f2.tmp" ) \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed, but only for one resource). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event23@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +| tee out8e.tmp \ +| grep -q "event23@example.com" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_quota_recurring.sh --- a/tests/test_resource_invitation_constraints_quota_recurring.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_quota_recurring.sh Fri Apr 22 16:22:58 2016 +0200 @@ -4,10 +4,7 @@ USER="mailto:resource-car-porsche911@example.com" SENDER="mailto:paul.boddie@example.com" -FBFILE="$STORE/$USER/freebusy" -FBSENDERFILE="$STORE/$SENDER/freebusy" QUOTA="$USER" -JOURNALFILE="$JOURNAL/$QUOTA/journal/$SENDER" mkdir -p "$PREFS/$USER" echo 'Europe/Oslo' > "$PREFS/$USER/TZID" @@ -19,8 +16,7 @@ # Employ a user-specific quota (no argument with the functions above). -mkdir -p "$JOURNAL/$QUOTA" -echo '* PT10H' > "$JOURNAL/$QUOTA/limits" +"$SET_QUOTA_LIMIT" "$QUOTA" '*' 'PT10H' $SET_QUOTA_LIMIT_ARGS "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-car-all.txt" 2>> $ERROR \ | "$SHOWMAIL" \ @@ -35,8 +31,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-recurring.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ -&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out0f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out0f.tmp" \ +&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out0f.tmp" \ && echo "Success" \ || echo "Failed" @@ -52,16 +51,20 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out1f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1f.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out1f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is not confirmed). - ! [ -e "$JOURNALFILE" ] \ -|| ! grep -q "event24@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out1e.tmp + + ! grep -q "event24@example.com" "out1e.tmp" \ && echo "Success" \ || echo "Failed" @@ -70,9 +73,12 @@ sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=11/;' "$TEMPLATES/event-request-car-recurring.txt" \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ -&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "$FBSENDERFILE" \ -&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out1s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1s.tmp" \ +&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "out1s.tmp" \ +&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out1s.tmp" \ && echo "Success" \ || echo "Failed" @@ -89,17 +95,21 @@ && echo "Success" \ || echo "Failed" - ! [ -e "$FBFILE" ] \ -|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ - && ! grep -q "^20141205T150000Z${TAB}20141205T160000Z" "$FBFILE" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out2f.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ +&& ! grep -q "^20141205T150000Z${TAB}20141205T160000Z" "out2f.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out2f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed). - ! [ -e "$JOURNALFILE" ] \ -|| ! grep -q "event24@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out2e.tmp + + ! grep -q "event24@example.com" "out2e.tmp" \ && echo "Success" \ || echo "Failed" @@ -108,9 +118,12 @@ sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=10/;' "$TEMPLATES/event-request-car-recurring.txt" \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ -&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "$FBSENDERFILE" \ -&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBSENDERFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER" "freebusy" \ +> out2s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2s.tmp" \ +&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "out2s.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out2s.tmp" \ && echo "Success" \ || echo "Failed" @@ -118,25 +131,29 @@ sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=10/;' "$TEMPLATES/event-request-car-recurring.txt" \ | "$RESOURCE_SCRIPT" $ARGS 2>> $ERROR \ -| tee out2r.tmp \ +| tee out3r.tmp \ | "$SHOWMAIL" \ -> out2.tmp +> out3.tmp - grep -q 'METHOD:REPLY' out2.tmp \ -&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out2.tmp \ + grep -q 'METHOD:REPLY' out3.tmp \ +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out3.tmp \ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE" ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE" \ -&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "$FBFILE" \ -&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy" \ +> out3f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3f.tmp" \ +&& grep -q "^20141205T150000Z${TAB}20141205T160000Z" "out3f.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out3f.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed). - [ -e "$JOURNALFILE" ] \ -&& grep -q "event24@example.com" "$JOURNALFILE" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER" \ +> out3e.tmp + + grep -q "event24@example.com" "out3e.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_constraints_quota_recurring_limits.sh --- a/tests/test_resource_invitation_constraints_quota_recurring_limits.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_constraints_quota_recurring_limits.sh Fri Apr 22 16:22:58 2016 +0200 @@ -8,13 +8,7 @@ SENDER2="mailto:vincent.vole@example.com" SENDERADDRESS1="paul.boddie@example.com" SENDERADDRESS2="vincent.vole@example.com" -FBFILE1="$STORE/$USER1/freebusy" -FBFILE2="$STORE/$USER2/freebusy" -FBSENDERFILE1="$STORE/$SENDER1/freebusy" -FBSENDERFILE2="$STORE/$SENDER2/freebusy" QUOTA=cars -JOURNALFILE1="$JOURNAL/$QUOTA/journal/$SENDER1" -JOURNALFILE2="$JOURNAL/$QUOTA/journal/$SENDER2" mkdir -p "$PREFS/$USER1" echo 'Europe/Oslo' > "$PREFS/$USER1/TZID" @@ -32,11 +26,8 @@ check_quota $QUOTA EOF -mkdir -p "$JOURNAL/$QUOTA" -cat > "$JOURNAL/$QUOTA/limits" <> $ERROR \ | "$SHOWMAIL" \ @@ -51,8 +42,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-cars-recurring.txt" 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE1" \ -&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBSENDERFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER1" "freebusy" \ +> out0f.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out0f.tmp" \ +&& grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out0f.tmp" \ && echo "Success" \ || echo "Failed" @@ -68,19 +62,25 @@ && echo "Success" \ || echo "Failed" - ( ! [ -e "$FBFILE1" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE1" )) \ -&& ( ! [ -e "$FBFILE2" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE2" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE2" )) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out1f.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out1f2.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1f.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out1f.tmp" \ +&& ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1f2.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out1f2.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is not confirmed). - ! [ -e "$JOURNALFILE1" ] \ -|| ! grep -q "event25@example.com" "$JOURNALFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER1" \ +> out1e.tmp + + ! grep -q "event25@example.com" "out1e.tmp" \ && echo "Success" \ || echo "Failed" @@ -89,8 +89,11 @@ sed 's/FREQ=DAILY/FREQ=DAILY;COUNT=5/;' "$TEMPLATES/event-request-cars-recurring.txt" \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE1" \ -&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBSENDERFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER1" "freebusy" \ +> out1s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out1s.tmp" \ +&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out1s.tmp" \ && echo "Success" \ || echo "Failed" @@ -118,25 +121,29 @@ && echo "Success" \ || echo "Failed" - (( ! [ -e "$FBFILE1" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ - && ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE1" )) \ - && [ -e "$FBFILE2" ] \ - && grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE2" \ - && grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE2" ) \ -|| (( ! [ -e "$FBFILE2" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE2" \ - && ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE2" )) \ - && [ -e "$FBFILE1" ] \ - && grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ - && grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE1" ) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out2f.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out2f2.tmp + + ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ + && ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out2f.tmp" \ + && grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f2.tmp" \ + && grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out2f2.tmp" ) \ +|| ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f2.tmp" \ + && ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out2f2.tmp" \ + && grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2f.tmp" \ + && grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out2f.tmp" ) \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed for one resource). - ! [ -e "$JOURNALFILE1" ] \ -|| grep -q "event25@example.com" "$JOURNALFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER1" \ +> out2e.tmp + + grep -q "event25@example.com" "out2e.tmp" \ && echo "Success" \ || echo "Failed" @@ -144,8 +151,11 @@ "$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-cars-recurring.txt" 2>> $ERROR - ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE1" \ -&& ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBSENDERFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER1" "freebusy" \ +> out2s.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out2s.tmp" \ +&& ! grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out2s.tmp" \ && echo "Success" \ || echo "Failed" @@ -160,19 +170,25 @@ && echo "Success" \ || echo "Failed" - ( ! [ -e "$FBFILE1" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE1" )) \ -&& ( ! [ -e "$FBFILE2" ] \ - || ( ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE2" \ - && ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "$FBFILE2" )) \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out3f.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out3f2.tmp + + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3f.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out3f.tmp" \ +&& ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3f2.tmp" \ +&& ! grep -q "^20141206T150000Z${TAB}20141206T160000Z" "out3f2.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is retracted). - ! [ -e "$JOURNALFILE1" ] \ -|| ! grep -q "event25@example.com" "$JOURNALFILE1" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER1" \ +> out3e.tmp + + ! grep -q "event25@example.com" "out3e.tmp" \ && echo "Success" \ || echo "Failed" @@ -182,8 +198,11 @@ | sed "s/$SENDERADDRESS1/$SENDERADDRESS2/;" \ | "$OUTGOING_SCRIPT" $ARGS 2>> $ERROR - grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE2" \ -&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBSENDERFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$SENDER2" "freebusy" \ +> out3s.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out3s.tmp" \ +&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out3s.tmp" \ && echo "Success" \ || echo "Failed" @@ -211,18 +230,24 @@ && echo "Success" \ || echo "Failed" - [ -e "$FBFILE1" ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ -&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE1" \ -&& [ -e "$FBFILE2" ] \ -&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE2" \ -&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "$FBFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER1" "freebusy" \ +> out4f.tmp + + "$LIST_SCRIPT" $LIST_ARGS "$USER2" "freebusy" \ +> out4f2.tmp + + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out4f.tmp" \ +&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out4f.tmp" \ +&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "out4f2.tmp" \ +&& grep -q "^20141130T150000Z${TAB}20141130T160000Z" "out4f2.tmp" \ && echo "Success" \ || echo "Failed" # Check the quota (event is confirmed for both resources). - [ -e "$JOURNALFILE2" ] \ -&& grep -q "event25@example.com" "$JOURNALFILE2" \ + "$LIST_SCRIPT" $LIST_ARGS "$QUOTA" "entries" "$SENDER2" \ +> out4e.tmp + + grep -q "event25@example.com" "out4e.tmp" \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tests/test_resource_invitation_recurring_indefinitely.sh --- a/tests/test_resource_invitation_recurring_indefinitely.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tests/test_resource_invitation_recurring_indefinitely.sh Fri Apr 22 16:22:58 2016 +0200 @@ -39,7 +39,9 @@ "$FREEBUSY_SCRIPT" "$USER" $FREEBUSY_ARGS $ARGS 2>> $ERROR - grep -q 'event14@example.com' "$STORE/$USER/freebusy-providers" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_providers" \ +| tee out3p.tmp \ +| grep -q 'event14@example.com' \ && echo "Success" \ || echo "Failed" @@ -57,7 +59,10 @@ && echo "Success" \ || echo "Failed" - ! grep -q 'event14@example.com' "$STORE/$USER/freebusy-providers" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_providers" \ +> out4p.tmp + + ! grep -q 'event14@example.com' "out4p.tmp" \ && echo "Success" \ || echo "Failed" @@ -83,6 +88,8 @@ && echo "Success" \ || echo "Failed" - grep -q 'event14@example.com' "$STORE/$USER/freebusy-providers" \ + "$LIST_SCRIPT" $LIST_ARGS "$USER" "freebusy_providers" \ +| tee out6p.tmp \ +| grep -q 'event14@example.com' \ && echo "Success" \ || echo "Failed" diff -r 35958ffd9f83 -r 52476453b1e3 tools/config.sh --- a/tools/config.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/config.sh Fri Apr 22 16:22:58 2016 +0200 @@ -1,7 +1,16 @@ #!/bin/sh +STORE_TYPE=file IMIP_AGENT_USER=imip-agent IMIP_AGENT_GROUP=lmtp INSTALL_DIR=/var/lib/imip-agent WEB_INSTALL_DIR=/var/www/imip-agent CONFIG_DIR=/etc/imip-agent + +# Store-specific settings. + +# For STORE_TYPE=postgresql... + +POSTGRESQL_DB=imip_agent +POSTGRESQL_USERS="imip-agent www-data" +AS_POSTGRES="sudo -u postgres" diff -r 35958ffd9f83 -r 52476453b1e3 tools/copy_store.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/copy_store.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,177 @@ +#!/usr/bin/env python + +""" +Copy store information into another store. + +Copyright (C) 2014, 2015, 2016 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 os.path import abspath, split +import sys + +# Find the modules. + +try: + import imiptools +except ImportError: + parent = abspath(split(split(__file__)[0])[0]) + if split(parent)[1] == "imip-agent": + sys.path.append(parent) + +from imiptools import config +from imiptools.data import Object +from imiptools.stores import get_store, get_publisher, get_journal + +def copy_store(from_store, from_journal, to_store, to_journal): + + """ + Copy stored information from the specified 'from_store' and 'from_journal' + to the specified 'to_store' and 'to_journal' respectively. + """ + + # For each user... + + for user in from_store.get_users(): + + # Copy requests. + + requests = from_store.get_requests(user) + if requests: + to_store.set_requests(user, requests) + + # Copy events, both active and cancellations. + + for dirname in (None, "cancellations"): + + # Get event, recurrence information. + + for uid, recurrenceid in from_store.get_all_events(user, dirname=dirname): + d = from_store.get_event(user, uid, recurrenceid, dirname=dirname) + if d: + to_store.set_event(user, uid, recurrenceid, Object(d).to_node()) + if dirname == "cancellations": + to_store.cancel_event(user, uid, recurrenceid) + else: + print >>sys.stderr, "Event for %s with UID %s and RECURRENCE-ID %s not found in %s" % ( + (user, uid, recurrenceid or "null", dirname or "active events")) + + # Copy counter-proposals. + + if dirname is None: + for other in from_store.get_counters(user, uid, recurrenceid): + d = from_store.get_counter(user, other, uid, recurrenceid) + if d: + to_store.set_counter(user, other, Object(d).to_node(), uid, recurrenceid) + else: + print >>sys.stderr, "Counter-proposal for %s with UID %s and RECURRENCE-ID %s not found in %s" % ( + (user, uid, recurrenceid or "null", dirname or "active events")) + + # Copy free/busy information for the user. + + freebusy = from_store.get_freebusy(user) + if freebusy: + to_store.set_freebusy(user, freebusy) + + # Copy free/busy information for other users. + + for other in from_store.get_freebusy_others(user): + freebusy = from_store.get_freebusy_for_other(user, other) + if freebusy: + to_store.set_freebusy_for_other(user, freebusy, other) + + # Copy free/busy offers. + + offers = from_store.get_freebusy_offers(user) + if offers: + to_store.set_freebusy_offers(user, offers) + + # For each quota group... + + for quota in from_journal.get_quotas(): + + # Copy quota limits. + + for user_group, limit in from_journal.get_limits(quota).items(): + to_journal.set_limit(quota, user_group, limit) + + # Copy group mappings. + + for store_user, user_group in from_journal.get_groups(quota).items(): + to_journal.set_group(quota, store_user, user_group) + + # Copy journal details. + + for group in from_journal.get_quota_users(quota): + to_journal.set_entries(quota, group, from_journal.get_entries(quota, group)) + + # Copy individual free/busy details. + + for store_user in from_journal.get_freebusy_users(quota): + to_journal.set_freebusy(store_user, from_journal.get_freebusy(store_user)) + +# Main program. + +if __name__ == "__main__": + + # Interpret the command line arguments. + + from_store_args = [] + to_store_args = [] + l = ignored = [] + + for arg in sys.argv[1:]: + if arg in ("-t", "--to"): + l = to_store_args + elif arg in ("-f", "--from"): + l = from_store_args + else: + l.append(arg) + + if len(from_store_args) not in (0, 3) or len(to_store_args) != 3: + print >>sys.stderr, """\ +Usage: %s \\ + [ ( -f | --from ) ] \\ + ( -t | --to ) + +Need details of a destination store indicated by the -t or --to option. +In addition, details of a source store may be indicated by the -f or --from +option; otherwise, the currently-configured store is used. +""" % split(sys.argv[0])[1] + sys.exit(1) + + # Override defaults if indicated. + + getvalue = lambda value, pos=0, default=None: value and value[pos] or default + + from_store_type = getvalue(from_store_args, 0, config.STORE_TYPE) + from_store_dir = getvalue(from_store_args, 1) + from_journal_dir = getvalue(from_store_args, 2) + + to_store_type, to_store_dir, to_journal_dir = to_store_args + + # Obtain store-related objects. + + from_store = get_store(from_store_type, from_store_dir) + from_journal = get_journal(from_store_type, from_journal_dir) + + to_store = get_store(to_store_type, to_store_dir) + to_journal = get_journal(to_store_type, to_journal_dir) + + # Process the store. + + copy_store(from_store, from_journal, to_store, to_journal) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 tools/fix.sh --- a/tools/fix.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/fix.sh Fri Apr 22 16:22:58 2016 +0200 @@ -3,38 +3,45 @@ DIRNAME=`dirname "$0"` if [ -e "$DIRNAME/config.sh" ]; then - . "$DIRNAME/config.sh" + CONFIG="$DIRNAME/config.sh" + . "$CONFIG" else - . /etc/imip-agent/config.sh + CONFIG=/etc/imip-agent/config.sh + . "$CONFIG" fi PROGNAME=`basename "$0"` if [ "$1" = "--help" ]; then cat 1>&2 < [ [ [ ] ] ] ] +Usage: $PROGNAME + +Fix permissions for the stored and published data directories, operating on... -Fix permissions for the stored and published data directories, operating on the -given stored data and published data directories (or, respectively, -$INSTALL_DIR and $WEB_INSTALL_DIR if omitted). + * $INSTALL_DIR + * $WEB_INSTALL_DIR -Set ownership and membership using the given user and group (or, respectively, -$IMIP_AGENT_USER and $IMIP_AGENT_GROUP if omitted). +...respectively. + +Set ownership and membership to the user and group respectively given as +$IMIP_AGENT_USER and $IMIP_AGENT_GROUP. EOF exit 1 fi -INSTALL_DIR=${1:-$INSTALL_DIR} -WEB_INSTALL_DIR=${2:-$WEB_INSTALL_DIR} -USER=${3:-$IMIP_AGENT_USER} -GROUP=${4:-$IMIP_AGENT_GROUP} +chown -R "$IMIP_AGENT_USER" "$INSTALL_DIR" +chgrp -R "$IMIP_AGENT_GROUP" "$INSTALL_DIR" -chown -R "$USER" "$INSTALL_DIR" -chgrp -R "$GROUP" "$INSTALL_DIR" - -for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static \ - "$INSTALL_DIR"/journal ; do - chown -R "$USER" "$DIR" - chgrp -R "$GROUP" "$DIR" +for DIR in "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do + chown -R "$IMIP_AGENT_USER" "$DIR" + chgrp -R "$IMIP_AGENT_GROUP" "$DIR" chmod -R g+w "$DIR" done + +if [ "$STORE_TYPE" = "file" ]; then + for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/journal ; do + chown -R "$IMIP_AGENT_USER" "$DIR" + chgrp -R "$IMIP_AGENT_GROUP" "$DIR" + chmod -R g+w "$DIR" + done +fi diff -r 35958ffd9f83 -r 52476453b1e3 tools/init.sh --- a/tools/init.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/init.sh Fri Apr 22 16:22:58 2016 +0200 @@ -1,49 +1,169 @@ #!/bin/sh DIRNAME=`dirname "$0"` +CONFIG="$DIRNAME/config.sh" -if [ -e "$DIRNAME/config.sh" ]; then - . "$DIRNAME/config.sh" +if [ -e "$CONFIG" ]; then + . "$CONFIG" else - . /etc/imip-agent/config.sh + CONFIG=/etc/imip-agent/config.sh + . "$CONFIG" +fi + +SCHEMA="$DIRNAME/../conf/postgresql/schema.sql" + +if [ ! -e "$SCHEMA" ]; then + SCHEMA=/etc/imip-agent/postgresql/schema.sql fi PROGNAME=`basename "$0"` if [ "$1" = "--help" ]; then cat 1>&2 < [ [ [ ] ] ] ] +Usage: $PROGNAME + +Initialise stored and published data directories at... -Initialise stored and published data directories either at any specified -locations or, respectively, at $INSTALL_DIR and $WEB_INSTALL_DIR. + * $INSTALL_DIR + * $WEB_INSTALL_DIR -Set permissions to the given user and group or, respectively, to $IMIP_AGENT_USER +...respectively. + +Set permissions to the user and group respectively given as $IMIP_AGENT_USER and $IMIP_AGENT_GROUP. -Within the stored data directory (using $INSTALL_DIR as an example), the -following directories are created: +Within the stored data directory, the following directories will be created +(with STORE_TYPE currently set as "$STORE_TYPE"): - * $INSTALL_DIR/journal * $INSTALL_DIR/preferences - * $INSTALL_DIR/store +EOF -Within the published data directory (using $WEB_INSTALL_DIR as an example), the -following directory is created: + if [ "$STORE_TYPE" = "file" ]; then + cat 1>&2 <&2 <&2 <&2 <&2 <&2 -for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static \ - "$INSTALL_DIR"/journal ; do +for DIR in "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do mkdir -p "$DIR" - chown "$USER" "$DIR" - chgrp "$GROUP" "$DIR" + chown "$IMIP_AGENT_USER" "$DIR" + chgrp "$IMIP_AGENT_GROUP" "$DIR" chmod g+ws "$DIR" done + +# Initialise a file store. + +if [ "$STORE_TYPE" = "file" ]; then + + echo "Creating store and journal directories..." 1>&2 + + for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/journal ; do + mkdir -p "$DIR" + chown "$IMIP_AGENT_USER" "$DIR" + chgrp "$IMIP_AGENT_GROUP" "$DIR" + chmod g+ws "$DIR" + done + +# Initialise a PostgreSQL store. + +elif [ "$STORE_TYPE" = "postgresql" ]; then + + # Check for the database. + + echo "Checking for the database ${POSTGRESQL_DB}..." 1>&2 + + if $AS_POSTGRES psql -tA -c 'select datname from pg_database' postgres | grep -q ^"$POSTGRESQL_DB"$ ; then + cat 1>&2 <&2 + + if ! $AS_POSTGRES createdb "$POSTGRESQL_DB" ; then + cat 1>&2 <&2 + + if ! $AS_POSTGRES psql -q -f "$SCHEMA" "$POSTGRESQL_DB" ; then + cat 1>&2 <&2 + + if ! $AS_POSTGRES createuser -D -R -S "$USER" ; then + cat 1>&2 <&2 + + if ! $AS_POSTGRES psql -Atc '\dt' "$POSTGRESQL_DB" \ + | cut -d '|' -f 2 \ + | xargs -I{} $AS_POSTGRES psql -q -c "grant all privileges on table {} to \"$USER\"" "$POSTGRESQL_DB" ; then + + cat 1>&2 <&2 < [ [ [ ] ] ] +Usage: $PROGNAME Initialise a given calendar user within an existing installation, creating -resources within the given stored data and published data directories or, -respectively, within $INSTALL_DIR and $WEB_INSTALL_DIR. +resources within the given stored data and published data directories... + + * $INSTALL_DIR + * $WEB_INSTALL_DIR + +...respectively. -The resources will be defined as having the given system user as owner or, -if the user is omitted, the $IMIP_AGENT_USER as owner. +The resources will be defined as having $IMIP_AGENT_USER as owner. + +See $CONFIG for the settings used as described above. + +Example: + +$PROGNAME mailto:vincent.vole@example.com EOF exit 1 fi CALENDAR_USER=$1 -INSTALL_DIR=${2:-$INSTALL_DIR} -WEB_INSTALL_DIR=${3:-$WEB_INSTALL_DIR} -USER=${4:-$IMIP_AGENT_USER} + +if [ ! "$CALENDAR_USER" ]; then + cat 1>&2 <&2 <&2 + +for DIR in "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do mkdir -p "$DIR/$CALENDAR_USER" - chown "$USER" "$DIR/$CALENDAR_USER" + chown "$IMIP_AGENT_USER" "$DIR/$CALENDAR_USER" chmod g+ws "$DIR/$CALENDAR_USER" # Group privileges should already be set. done + +if [ "$STORE_TYPE" = "file" ]; then + + echo "Creating store and journal directories..." 1>&2 + + for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/journal ; do + mkdir -p "$DIR/$CALENDAR_USER" + chown "$IMIP_AGENT_USER" "$DIR/$CALENDAR_USER" + chmod g+ws "$DIR/$CALENDAR_USER" + # Group privileges should already be set. + done +fi diff -r 35958ffd9f83 -r 52476453b1e3 tools/install.sh --- a/tools/install.sh Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/install.sh Fri Apr 22 16:22:58 2016 +0200 @@ -28,6 +28,7 @@ for DIR in "$INSTALL_DIR/imiptools" \ "$INSTALL_DIR/imiptools/stores" \ + "$INSTALL_DIR/imiptools/stores/database" \ "$INSTALL_DIR/imiptools/handlers" \ "$INSTALL_DIR/imiptools/handlers/scheduling" ; do if [ ! -e "$DIR" ]; then @@ -45,6 +46,7 @@ cp imiptools/*.py "$INSTALL_DIR/imiptools/" cp imiptools/stores/*.py "$INSTALL_DIR/imiptools/stores/" +cp imiptools/stores/database/*.py "$INSTALL_DIR/imiptools/stores/database/" cp imiptools/handlers/*.py "$INSTALL_DIR/imiptools/handlers/" cp imiptools/handlers/scheduling/*.py "$INSTALL_DIR/imiptools/handlers/scheduling/" @@ -85,9 +87,23 @@ ln -s "$CONFIG_DIR/config.py" "$INSTALL_DIR/imiptools/config.py" +# Copy related configuration files. + +if [ ! -e "$CONFIG_DIR/postgresql" ]; then + mkdir -p "$CONFIG_DIR/postgresql" +fi + +if [ -e "$CONFIG_DIR/postgresql/schema.sql" ]; then + if ! cmp "conf/postgresql/schema.sql" "$CONFIG_DIR/postgresql/schema.sql" > /dev/null 2>&1 ; then + cp "conf/postgresql/schema.sql" "$CONFIG_DIR/postgresql/schema.sql.new" + fi +else + cp "conf/postgresql/schema.sql" "$CONFIG_DIR/postgresql/schema.sql" +fi + # Tools -TOOLS="fix.sh init.sh init_user.sh make_freebusy.py update_quotas.py update_scheduling_modules.py" +TOOLS="copy_store.py fix.sh init.sh init_user.sh make_freebusy.py set_quota_limit.py update_quotas.py update_scheduling_modules.py" if [ ! -e "$INSTALL_DIR/tools" ]; then mkdir -p "$INSTALL_DIR/tools" diff -r 35958ffd9f83 -r 52476453b1e3 tools/make_freebusy.py --- a/tools/make_freebusy.py Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/make_freebusy.py Fri Apr 22 16:22:58 2016 +0200 @@ -21,7 +21,7 @@ this program. If not, see . """ -from os.path import split +from os.path import abspath, split import sys # Find the modules. @@ -29,16 +29,17 @@ try: import imiptools except ImportError: - parent = split(split(__file__)[0])[0] + parent = abspath(split(split(__file__)[0])[0]) if split(parent)[1] == "imip-agent": sys.path.append(parent) from codecs import getwriter +from imiptools import config from imiptools.client import Client from imiptools.data import get_window_end, Object from imiptools.dates import get_default_timezone, to_utc_datetime -from imiptools.period import insert_period -from imiptools.stores.file import FileStore, FilePublisher, FileJournal +from imiptools.period import FreeBusyCollection +from imiptools.stores import get_store, get_publisher, get_journal def make_freebusy(client, participant, store_and_publish, include_needs_action, reset_updated_list, verbose): @@ -86,7 +87,7 @@ if not all_events: all_events = store.get_all_events(user) - fb = [] + fb = FreeBusyCollection() # With providers of additional periods, append to the existing collection. @@ -115,7 +116,7 @@ if obj.get_participation(partstat, include_needs_action): for p in obj.get_active_periods(recurrenceids, tzid, window_end): fbp = obj.get_freebusy_period(p, partstat == "ORG") - insert_period(fb, fbp) + fb.insert_period(fbp) # Store and publish the free/busy collection. @@ -148,6 +149,7 @@ participants = [] args = [] + store_type = [] store_dir = [] publishing_dir = [] journal_dir = [] @@ -163,6 +165,8 @@ if arg in ("-n", "-s", "-v", "-r"): args.append(arg) l = ignored + elif arg == "-T": + l = store_type elif arg == "-S": l = store_dir elif arg == "-P": @@ -178,20 +182,26 @@ user = participants[0] except IndexError: print >>sys.stderr, """\ -Usage: %s [ ] +Usage: %s [ ] [ ] Need a user and an optional participant (if different from the user), along with the -s option if updating the store and the published details. -Specify -n to include objects with PARTSTAT of NEEDS-ACTION. -Specify -r to inspect all objects, not just those expected to provide details. -Specify -v for additional messages on standard error. + +Specific options: + +-s Update the store and published details (write details to standard output + otherwise) +-n Include objects with PARTSTAT of NEEDS-ACTION +-r Inspect all objects, not just those expected to provide details +-v Show additional messages on standard error General options: --j indicate the journal directory location --p indicate the preferences directory location --P indicate the publishing directory location --S indicate the store directory location +-j Indicates the journal directory location +-p Indicates the preferences directory location +-P Indicates the publishing directory location +-S Indicates the store directory location +-T Indicates the store type (the configured value if omitted) """ % split(sys.argv[0])[1] sys.exit(1) @@ -205,16 +215,19 @@ # Override defaults if indicated. - store_dir = store_dir and store_dir[0] or None - publishing_dir = publishing_dir and publishing_dir[0] or None - journal_dir = journal_dir and journal_dir[0] or None - preferences_dir = preferences_dir and preferences_dir[0] or None + getvalue = lambda value, default=None: value and value[0] or default - # Obtain store-related objects. + store_type = getvalue(store_type, config.STORE_TYPE) + store_dir = getvalue(store_dir) + publishing_dir = getvalue(publishing_dir) + journal_dir = getvalue(journal_dir) + preferences_dir = getvalue(preferences_dir) - store = FileStore(store_dir) - publisher = FilePublisher(publishing_dir) - journal = FileJournal(journal_dir) + # Obtain store-related objects or delegate this to the Client initialiser. + + store = get_store(store_type, store_dir) + publisher = get_publisher(publishing_dir) + journal = get_journal(store_type, journal_dir) # Obtain a list of users for processing. diff -r 35958ffd9f83 -r 52476453b1e3 tools/sendmail.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/sendmail.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,9 @@ +#!/usr/bin/env python + +import smtplib +import sys + +sender, recipients = sys.argv[1], sys.argv[2:] + +s = smtplib.SMTP("localhost") +print s.sendmail(sender, recipients, sys.stdin.read()) diff -r 35958ffd9f83 -r 52476453b1e3 tools/set_quota_limit.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tools/set_quota_limit.py Fri Apr 22 16:22:58 2016 +0200 @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +""" +Set a quota limit for a user group. + +Copyright (C) 2016 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 os.path import abspath, split +import sys + +# Find the modules. + +try: + import imiptools +except ImportError: + parent = abspath(split(split(__file__)[0])[0]) + if split(parent)[1] == "imip-agent": + sys.path.append(parent) + +from imiptools import config +from imiptools.stores import get_journal + +# Main program. + +if __name__ == "__main__": + + # Interpret the command line arguments. + + args = [] + store_type = [] + journal_dir = [] + + # Collect quota details first, switching to other arguments when encountering + # switches. + + l = args + + for arg in sys.argv[1:]: + if arg == "-T": + l = store_type + elif arg == "-j": + l = journal_dir + else: + l.append(arg) + + try: + quota, group, limit = args + except ValueError: + print >>sys.stderr, """\ +Usage: %s [ ] + +General options: + +-j Indicates the journal directory location +-T Indicates the store type (the configured value if omitted) +""" % split(sys.argv[0])[1] + sys.exit(1) + + # Override defaults if indicated. + + getvalue = lambda value, default=None: value and value[0] or default + + store_type = getvalue(store_type, config.STORE_TYPE) + journal_dir = getvalue(journal_dir) + + # Obtain store-related objects. + + journal = get_journal(store_type, journal_dir) + journal.set_limit(quota, group, limit) + +# vim: tabstop=4 expandtab shiftwidth=4 diff -r 35958ffd9f83 -r 52476453b1e3 tools/update_quotas.py --- a/tools/update_quotas.py Tue Apr 19 21:20:57 2016 +0200 +++ b/tools/update_quotas.py Fri Apr 22 16:22:58 2016 +0200 @@ -19,7 +19,7 @@ this program. If not, see . """ -from os.path import split +from os.path import abspath, split import sys # Find the modules. @@ -27,14 +27,15 @@ try: import imiptools except ImportError: - parent = split(split(__file__)[0])[0] + parent = abspath(split(split(__file__)[0])[0]) if split(parent)[1] == "imip-agent": sys.path.append(parent) from codecs import getwriter +from imiptools import config from imiptools.dates import get_datetime, get_default_timezone, get_time, \ to_utc_datetime -from imiptools.stores.file import FileJournal +from imiptools.stores import get_journal def remove_expired_entries(entries, expiry): @@ -110,6 +111,7 @@ quotas = [] args = [] + store_type = [] journal_dir = [] expiry = [] ignored = [] @@ -123,6 +125,8 @@ if arg in ("-s", "-v"): args.append(arg) l = ignored + elif arg == "-T": + l = store_type elif arg == "-j": l = journal_dir elif arg == "-e": @@ -141,8 +145,9 @@ General options: --e indicate an expiry time for events (default is now) --j indicate the journal directory location +-e Indicates an expiry time for events (default is now) +-j Indicates the journal directory location +-T Indicates the store type (the configured value if omitted) """ % split(sys.argv[0])[1] sys.exit(1) @@ -153,8 +158,11 @@ # Override defaults if indicated. - journal_dir = journal_dir and journal_dir[0] or None - expiry = expiry and expiry[0] or None + getvalue = lambda value, default=None: value and value[0] or default + + store_type = getvalue(store_type, config.STORE_TYPE) + journal_dir = getvalue(journal_dir) + expiry = getvalue(expiry) if expiry: expiry = to_utc_datetime(get_datetime(expiry), get_default_timezone()) @@ -164,7 +172,7 @@ # Obtain store-related objects. - journal = FileJournal(journal_dir) + journal = get_journal(store_type, journal_dir) # Obtain a list of users for processing.