1.1 --- a/docs/wiki/Administration Sun Feb 07 23:35:20 2016 +0100 1.2 +++ b/docs/wiki/Administration Mon Feb 08 00:14:53 2016 +0100 1.3 @@ -114,10 +114,11 @@ 1.4 identity in [[../MailIntegration/Simple|lists of addresses]] or by adjusting 1.5 [[../MailIntegration/LDAP|queries yielding calendar users]]. 1.6 1.7 -== Adding Scheduling Functions == 1.8 +== Adding Scheduling-Related Functions == 1.9 1.10 -The `scheduling_function` setting employs functions that reside within modules in 1.11 -the `imiptools.handlers.scheduling` package. Extra modules can be installed in 1.12 +The `confirmation_function`, `retraction_function` and `scheduling_function` 1.13 +settings employ functions that reside within modules in the 1.14 +`imiptools.handlers.scheduling` package. Extra modules can be installed in 1.15 this package by adding files to the `scheduling` directory within the software 1.16 installation. 1.17 1.18 @@ -129,4 +130,5 @@ 1.19 1.20 It is envisaged that the installation of additional scheduling modules and the 1.21 use of this tool will be performed by the packaging system provided by an 1.22 -operating system distribution. 1.23 +operating system distribution. The `tools/install.sh` script runs the above 1.24 +tool as part of the installation process.
2.1 --- a/docs/wiki/FilesystemUsage Sun Feb 07 23:35:20 2016 +0100 2.2 +++ b/docs/wiki/FilesystemUsage Mon Feb 08 00:14:53 2016 +0100 2.3 @@ -7,31 +7,78 @@ 2.4 {{{#!table 2.5 '''Resource''' || '''Default Location''' || '''Purpose''' 2.6 == 2.7 -Store || `/var/lib/imip-agent/store` 2.8 -|| Per-user directories containing calendar objects and scheduling information 2.9 +Free/busy || `/var/www/imip-agent/static` 2.10 +|| Per-user directories containing [[../FreeBusyPublishing|free/busy resources]] 2.11 +.. for publication over the Web 2.12 +== 2.13 +Journal || `/var/lib/imip-agent/journal` 2.14 +|| Per-quota directories containing journal information recording 2.15 +.. [[../Resources|resource]] usage 2.16 == 2.17 Preferences || `/var/lib/imip-agent/preferences` 2.18 || Per-user directories containing [[../Preferences|preferences]] controlling 2.19 .. each user's experience of the software 2.20 == 2.21 -Free/busy || `/var/www/imip-agent/static` 2.22 -|| Per-user directories containing [[../FreeBusyPublishing|free/busy resources]] 2.23 -.. for publication over the Web 2.24 +Store || `/var/lib/imip-agent/store` 2.25 +|| Per-user directories containing calendar objects and scheduling information 2.26 }}} 2.27 2.28 Note that the free/busy resources are located in `/var/www` as opposed to 2.29 -`/var/lib`. 2.30 +`/var/lib` since they are intended to be published on the Web. 2.31 + 2.32 +== Journal Structure == 2.33 + 2.34 +Within the journal directory are a collection of subdirectories, each of which 2.35 +represent a distinct quota group for one or more resources. When a user attempts 2.36 +to reserve a resource in such a group, their ability to schedule that resource 2.37 +will depend on how much they are using the other resources in that group. 2.38 + 2.39 +The directory for each quota group contains the following entries: 2.40 + 2.41 +{{{{#!table 2.42 +'''Entry''' || '''Purpose''' 2.43 +== 2.44 +`freebusy` 2.45 +|| A directory containing files, one per user, each containing period descriptions 2.46 +.. for reservations made by that user, in chronological order, structured 2.47 +.. similarly to the `freebusy` file found in each user's own store; the record is 2.48 +.. consolidated for all resources in a quota group, but it is not consolidated for 2.49 +.. groups of users 2.50 +{{{ 2.51 +freebusy/USER 2.52 +}}} 2.53 +== 2.54 +`groups` 2.55 +|| A mapping from user identities to group identifiers indicating the sharing 2.56 +.. of a quota across a number of users 2.57 +== 2.58 +`journal` 2.59 +|| A directory containing transaction files, one per user or user group, 2.60 +.. describing confirmed reservations and retracted (cancelled) reservations for 2.61 +.. that user or group 2.62 +{{{ 2.63 +journal/GROUP 2.64 +journal/USER 2.65 +}}} 2.66 +== 2.67 +`limits` 2.68 +|| A mapping from user identities or group identifiers to quota limits 2.69 +}}}} 2.70 2.71 == Store Structure == 2.72 2.73 +Within the store directory are a collection of user-specific subdirectories 2.74 +acting as each user's own store directory containing various files and further 2.75 +subdirectories. 2.76 + 2.77 The store directory for each user is considered in isolation from all other 2.78 -users' directories: imip-agent does not go looking for information belonging to 2.79 -other users when processing information on behalf of a particular user. 2.80 +users' directories: imip-agent ''does not'' go looking for information belonging 2.81 +to other users when processing information on behalf of a particular user. 2.82 2.83 -The following subdirectories are defined within a user's store directory: 2.84 +The following entries are defined within a user's own store directory: 2.85 2.86 {{{{#!table 2.87 -'''Directory''' || '''Purpose''' 2.88 +'''Entry''' || '''Purpose''' 2.89 == 2.90 `cancellations` 2.91 || Retains cancelled event details in `objects` and `recurrences` structures 2.92 @@ -65,6 +112,9 @@ 2.93 || A collection of files, one per user, each containing period descriptions 2.94 .. received or deduced for that user in chronological order, structured similarly 2.95 .. to the store user's own `freebusy` file 2.96 +{{{ 2.97 +freebusy-other/USER 2.98 +}}} 2.99 == 2.100 `freebusy-providers` 2.101 || A file containing details of [[../EventRecurrences|recurring events]] for which
3.1 --- a/docs/wiki/Preferences Sun Feb 07 23:35:20 2016 +0100 3.2 +++ b/docs/wiki/Preferences Mon Feb 08 00:14:53 2016 +0100 3.3 @@ -110,6 +110,24 @@ 3.4 .. all event details 3.5 }}} 3.6 3.7 +=== confirmation_function === 3.8 + 3.9 + Default:: (none) 3.10 + Alternatives:: (see below) 3.11 + 3.12 +Indicates the confirmation functions used by [[../Resources|resources]] to be 3.13 +invoked when an event is scheduled. Such functions support certain scheduling 3.14 +functions that require a record of scheduling activity. 3.15 + 3.16 +The `imiptools.handlers.scheduling` module contains the built-in confirmation 3.17 +functions which include the following: 3.18 + 3.19 +{{{#!table 3.20 +`add_to_quota` || add the details of an event to quota records 3.21 +}}} 3.22 + 3.23 +See also `retraction_function` and `scheduling_function`. 3.24 + 3.25 === event_refreshing === 3.26 3.27 Default:: `never` 3.28 @@ -267,6 +285,24 @@ 3.29 minute values that correspond to permitted values in this participant's own 3.30 time zone. 3.31 3.32 +=== retraction_function === 3.33 + 3.34 + Default:: (none) 3.35 + Alternatives:: (see below) 3.36 + 3.37 +Indicates the retraction functions used by [[../Resources|resources]] to be 3.38 +invoked when an event is cancelled. Such functions support certain scheduling 3.39 +functions that require a record of scheduling activity. 3.40 + 3.41 +The `imiptools.handlers.scheduling` module contains the built-in retraction 3.42 +functions which include the following: 3.43 + 3.44 +{{{#!table 3.45 +`remove_from_quota` || remove the details of an event from quota records 3.46 +}}} 3.47 + 3.48 +See also `confirmation_function` and `scheduling_function`. 3.49 + 3.50 === scheduling_function === 3.51 3.52 Default:: `schedule_in_freebusy` 3.53 @@ -277,7 +313,7 @@ 3.54 scheduling request appearing on a separate line, optionally accompanied by 3.55 arguments controlling the behaviour of the function. 3.56 3.57 -The imiptools.handlers.scheduling module contains the built-in scheduling 3.58 +The `imiptools.handlers.scheduling` module contains the built-in scheduling 3.59 functions which include the following: 3.60 3.61 {{{#!table 3.62 @@ -286,6 +322,10 @@ 3.63 .. the invitation according to the indicated rules 3.64 .. (described in the [[../Resources|resources guide]]) 3.65 == 3.66 +`check_quota` || accept an invitation only if the organiser does not 3.67 + .. exceed their quota allowance for bookings 3.68 + .. (described in the [[../Resources|resources guide]]) 3.69 +== 3.70 `same_domain_only` || accept an invitation only if the organiser employs an 3.71 .. address in the same domain as the resource 3.72 == 3.73 @@ -309,3 +349,5 @@ 3.74 3.75 The scheduling mechanism can be extended by implementing additional scheduling 3.76 functions or by extending the handler framework directly. 3.77 + 3.78 +See also `confirmation_function` and `retraction_function`.
4.1 --- a/docs/wiki/Resources Sun Feb 07 23:35:20 2016 +0100 4.2 +++ b/docs/wiki/Resources Mon Feb 08 00:14:53 2016 +0100 4.3 @@ -17,6 +17,92 @@ 4.4 4.5 <<TableOfContents(2,4)>> 4.6 4.7 +== Confirmation and Retraction Functions == 4.8 + 4.9 +Confirmation and retraction functions are used to update other resources and 4.10 +systems with the details of scheduled events. When an event is successfully 4.11 +scheduled (according to the scheduling functions), all registered confirmation 4.12 +functions are invoked to perform such notifications. Similarly, when an event 4.13 +is cancelled, all registered retraction functions are invoked to inform other 4.14 +components of the removal of the event from schedules. 4.15 + 4.16 +The [[../Preferences#confirmation_function|confirmation_function]] and 4.17 +[[../Preferences#retraction_function|retraction_function]] settings indicate 4.18 +the behaviour of a resource when such circumstances arise. By themselves, 4.19 +these settings do no more than keep a kind of journal of scheduling events, 4.20 +but certain scheduling functions may build upon such journals to make 4.21 +scheduling decisions. For example: 4.22 + 4.23 +{{{{#!table 4.24 +'''All Functions''' || '''Decision Process''' 4.25 +== 4.26 +<style="vertical-align: top;"> 4.27 + 4.28 +Scheduling functions: 4.29 + 4.30 +{{{ 4.31 +check_quota 4.32 +}}} 4.33 + 4.34 +Confirmation functions: 4.35 + 4.36 +{{{ 4.37 +add_to_quota 4.38 +}}} 4.39 + 4.40 +Retraction functions: 4.41 + 4.42 +{{{ 4.43 +remove_from_quota 4.44 +}}} 4.45 + 4.46 +|| 4.47 + 4.48 +{{{#!graphviz 4.49 +//format=svg 4.50 +//transform=notugly 4.51 +digraph scheduling_decisions { 4.52 + node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 4.53 + edge [tooltip="Scheduling decisions"]; 4.54 + 4.55 + subgraph { 4.56 + rank=same; 4.57 + mail [label="Incoming mail\nfrom vincent.vole@example.com",shape=folder,style=filled,fillcolor=cyan]; 4.58 + cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 4.59 + } 4.60 + 4.61 + subgraph { 4.62 + rank=same; 4.63 + check_quota [label="Is allowed by quota?",shape=ellipse,style=filled,fillcolor=gold]; 4.64 + quota [label="Quota for resource",shape=folder]; 4.65 + quota_for_vole [label="...applying to\nvincent.vole@example.com",shape=folder]; 4.66 + } 4.67 + 4.68 + schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.69 + 4.70 + subgraph { 4.71 + rank=same; 4.72 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.73 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.74 + } 4.75 + 4.76 + add_to_quota [label="Add to quota",shape=ellipse,style=filled,fillcolor=darkorange]; 4.77 + remove_from_quota [label="Remove from quota",shape=ellipse,style=filled,fillcolor=darkorange]; 4.78 + 4.79 + mail -> check_quota -> schedule -> accept; 4.80 + check_quota -> decline [style=dashed]; 4.81 + schedule -> add_to_quota -> quota; 4.82 + quota -> quota_for_vole -> check_quota; 4.83 + 4.84 + cancel -> remove_from_quota -> quota; 4.85 +} 4.86 +}}} 4.87 + 4.88 +}}}} 4.89 + 4.90 +See the [[#Quota_Controls|quota controls]] documentation for more information 4.91 +about applying quotas to resources. 4.92 + 4.93 == Scheduling Functions == 4.94 4.95 The [[../Preferences#scheduling_function|scheduling_function]] setting 4.96 @@ -51,8 +137,12 @@ 4.97 } 4.98 4.99 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.100 - accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.101 - decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.102 + 4.103 + subgraph { 4.104 + rank=same; 4.105 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.106 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.107 + } 4.108 4.109 mail -> schedule_in_freebusy -> schedule -> accept; 4.110 schedule_in_freebusy -> decline [style=dashed]; 4.111 @@ -109,8 +199,12 @@ 4.112 same_domain_only [label="Organiser has resource domain?",shape=ellipse,style=filled,fillcolor=gold]; 4.113 4.114 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.115 - accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.116 - decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.117 + 4.118 + subgraph { 4.119 + rank=same; 4.120 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.121 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.122 + } 4.123 4.124 mail -> schedule_in_freebusy -> same_domain_only -> schedule -> accept; 4.125 schedule_in_freebusy -> decline [style=dashed]; 4.126 @@ -174,8 +268,12 @@ 4.127 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 4.128 4.129 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.130 - accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.131 - decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.132 + 4.133 + subgraph { 4.134 + rank=same; 4.135 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.136 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.137 + } 4.138 4.139 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> end_acl -> schedule -> accept; 4.140 end_acl -> decline [style=dashed]; 4.141 @@ -252,8 +350,12 @@ 4.142 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 4.143 4.144 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.145 - accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.146 - decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.147 + 4.148 + subgraph { 4.149 + rank=same; 4.150 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.151 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.152 + } 4.153 4.154 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> decline_attendee -> end_acl -> schedule -> accept; 4.155 end_acl -> decline [style=dashed]; 4.156 @@ -317,8 +419,12 @@ 4.157 end_acl [label="end",shape=ellipse,style=filled,fillcolor=darkorange]; 4.158 4.159 schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.160 - accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.161 - decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.162 + 4.163 + subgraph { 4.164 + rank=same; 4.165 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.166 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.167 + } 4.168 4.169 mail -> schedule_in_freebusy -> access_control_list -> accept_default -> decline_attendee -> accept_organiser -> end_acl -> schedule -> accept; 4.170 end_acl -> decline [style=dashed]; 4.171 @@ -332,3 +438,243 @@ 4.172 4.173 Here, the stated organiser may still arrange a booking of the resource where 4.174 the previously-mentioned attendee is involved. 4.175 + 4.176 +=== Quota Controls === 4.177 + 4.178 +The [[#Confirmation_and_Retraction_Functions|confirmation and retraction functions]] 4.179 +section provides an example of applying quotas to event participants. However, 4.180 +this section describes the operation of the quota system in more detail. 4.181 + 4.182 +In contrast to each user's stored information which consolidates information 4.183 +related to that user's own schedule, the quota system consolidates information 4.184 +related to the schedules of one or more resources, thus enabling observations 4.185 +to be made about their collective usage. 4.186 + 4.187 +First, consider a resource such as a car where an organiser of an event may be 4.188 +booking the car for travel purposes. A quota prevents the organiser from 4.189 +booking the resource too much and denying other users access to it. 4.190 + 4.191 +Now consider a number of separate car resources. An organiser might attempt to 4.192 +get around any individual resource quota by booking a number of different cars. 4.193 +By grouping the resources together, the organiser will exhaust a quota set on 4.194 +the group of resources as reservations are made for the different members of 4.195 +the group. 4.196 + 4.197 +==== Initialising Quotas ==== 4.198 + 4.199 +Within the journal storage area (described in the [[../FilesystemUsage|filesystem guide]]), 4.200 +a quota group directory must be initialised with a `limits` file indicating 4.201 +the amount of time that can be occupied by the cumulative total of all events 4.202 +scheduled by an individual user or a group of which they are a member. For 4.203 +example: 4.204 + 4.205 +{{{ 4.206 +mailto:vincent.vole@example.com PT10H 4.207 +}}} 4.208 + 4.209 +This indicates that the given user may only reserve 10 hours of events or less. 4.210 +Attempts to schedule more time will be declined. 4.211 + 4.212 +To impose a general quota, the special `*` identity can be used: 4.213 + 4.214 +{{{ 4.215 +* PT10H 4.216 +}}} 4.217 + 4.218 +When a user identity is not listed and no general quota is defined, that 4.219 +particular user will be unable to reserve the resource unless defined as a 4.220 +member of a group listed in the `limits` file, as described below. 4.221 + 4.222 +==== Sharing Quotas Across Users ==== 4.223 + 4.224 +When the use of resources is to be shared between users in such a way that 4.225 +groups of users will be sharing a single quota, the `groups` file in the 4.226 +quota directory must be defined, mapping each user identity to the group to 4.227 +which they will belong. For example: 4.228 + 4.229 +{{{ 4.230 +mailto:vincent.vole@example.com developers 4.231 +mailto:harvey.horse@example.com developers 4.232 +mailto:paul.boddie@example.com developers 4.233 +mailto:simon.skunk@example.com testers 4.234 +}}} 4.235 + 4.236 +The group identity can then be employed in the `limits` file: 4.237 + 4.238 +{{{ 4.239 +developers PT10H 4.240 +testers PT20H 4.241 +}}} 4.242 + 4.243 +Limits apply to individuals, then to groups, then the general quota applies. 4.244 +Thus, when a group is not listed, the general quota applies; without a general 4.245 +quota (and without matching individually), a group member will be unable to 4.246 +reserve the resource. 4.247 + 4.248 +==== Individual Resource Quotas ==== 4.249 + 4.250 +The trivial case of applying quotas is to give a resource its own quota. This 4.251 +is achieved by not specifying any arguments to the `check_quota` scheduling 4.252 +function or to the `add_to_quota` and `remove_from_quota` functions. See the 4.253 +[[#Confirmation_and_Retraction_Functions|confirmation and retraction functions]] 4.254 +section for an example of this. 4.255 + 4.256 +==== Common Resource Quotas ==== 4.257 + 4.258 +By indicating an argument to the different functions, a common quota can be 4.259 +employed. In the following example, both resources would employ the given 4.260 +function invocations to pool their knowledge about their schedules. 4.261 + 4.262 +{{{{#!table 4.263 +'''All Functions''' || '''Decision Process''' 4.264 +== 4.265 +<style="vertical-align: top;"> 4.266 + 4.267 +Scheduling functions: 4.268 + 4.269 +{{{ 4.270 +check_quota cars 4.271 +}}} 4.272 + 4.273 +Confirmation functions: 4.274 + 4.275 +{{{ 4.276 +add_to_quota cars 4.277 +}}} 4.278 + 4.279 +Retraction functions: 4.280 + 4.281 +{{{ 4.282 +remove_from_quota cars 4.283 +}}} 4.284 + 4.285 +|| 4.286 + 4.287 +{{{#!graphviz 4.288 +//format=svg 4.289 +//transform=notugly 4.290 +digraph scheduling_decisions { 4.291 + node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 4.292 + edge [tooltip="Scheduling decisions"]; 4.293 + 4.294 + subgraph { 4.295 + rank=same; 4.296 + mail_cadillac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-cadillac@example.com",shape=folder,style=filled,fillcolor=cyan]; 4.297 + mail_pontiac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-pontiac@example.com",shape=folder,style=filled,fillcolor=cyan]; 4.298 + cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 4.299 + } 4.300 + 4.301 + subgraph { 4.302 + rank=same; 4.303 + check_quota [label="Is allowed by quota?",shape=ellipse,style=filled,fillcolor=gold]; 4.304 + quota_cars [label="Quota for cars",shape=folder]; 4.305 + quota_cars_vole [label="...applying to\nvincent.vole@example.com",shape=folder]; 4.306 + } 4.307 + 4.308 + schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.309 + 4.310 + subgraph { 4.311 + rank=same; 4.312 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.313 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.314 + } 4.315 + 4.316 + add_to_quota [label="Add to quota",shape=ellipse,style=filled,fillcolor=darkorange]; 4.317 + remove_from_quota [label="Remove from quota",shape=ellipse,style=filled,fillcolor=darkorange]; 4.318 + 4.319 + mail_cadillac -> check_quota; 4.320 + mail_pontiac -> check_quota -> schedule -> accept; 4.321 + check_quota -> decline [style=dashed]; 4.322 + schedule -> add_to_quota -> quota_cars; 4.323 + quota_cars -> quota_cars_vole -> check_quota; 4.324 + 4.325 + cancel -> remove_from_quota -> quota_cars; 4.326 +} 4.327 +}}} 4.328 + 4.329 +}}}} 4.330 + 4.331 +==== Collective Scheduling ==== 4.332 + 4.333 +Consider two separate resources: both may be reserved at the same time by the 4.334 +same organiser; neither resource would normally decline the reservation on the 4.335 +basis of schedule availability, should the period concerned be free. However, 4.336 +it may be undesirable for one organiser to occupy both resources at the same 4.337 +time. 4.338 + 4.339 +Consequently, a mechanism is required to pool the resource schedules in such a 4.340 +way that any reservation performed for one resource at a given point in time 4.341 +prohibits another reservation performed for a related resource at the same 4.342 +point in time by the same user. 4.343 + 4.344 +The free/busy records held for a given quota group permit such collective 4.345 +scheduling decisions and are employed as follows: 4.346 + 4.347 +{{{{#!table 4.348 +'''All Functions''' || '''Decision Process''' 4.349 +== 4.350 +<style="vertical-align: top;"> 4.351 + 4.352 +Scheduling functions: 4.353 + 4.354 +{{{ 4.355 +schedule_within_quota cars 4.356 +}}} 4.357 + 4.358 +Confirmation functions: 4.359 + 4.360 +{{{ 4.361 +add_to_quota_freebusy cars 4.362 +}}} 4.363 + 4.364 +Retraction functions: 4.365 + 4.366 +{{{ 4.367 +remove_from_quota_freebusy cars 4.368 +}}} 4.369 + 4.370 +|| 4.371 + 4.372 +{{{#!graphviz 4.373 +//format=svg 4.374 +//transform=notugly 4.375 +digraph scheduling_decisions { 4.376 + node [shape=box,fontsize="13.0",fontname="Helvetica",tooltip="Scheduling decisions"]; 4.377 + edge [tooltip="Scheduling decisions"]; 4.378 + 4.379 + subgraph { 4.380 + rank=same; 4.381 + mail_cadillac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-cadillac@example.com",shape=folder,style=filled,fillcolor=cyan]; 4.382 + mail_pontiac [label="Incoming mail\nfrom vincent.vole@example.com\nto resource-car-pontiac@example.com",shape=folder,style=filled,fillcolor=cyan]; 4.383 + cancel [label="Incoming cancellation",shape=folder,style=filled,fillcolor=cyan]; 4.384 + } 4.385 + 4.386 + subgraph { 4.387 + rank=same; 4.388 + schedule_across_quota [label="Can be scheduled within the quota?",shape=ellipse,style=filled,fillcolor=gold]; 4.389 + quota_cars [label="Quota for cars",shape=folder]; 4.390 + freebusy_cars_vole [label="...recording schedule for\nvincent.vole@example.com",shape=folder]; 4.391 + } 4.392 + 4.393 + schedule [label="Schedule event for resource",shape=ellipse,style=filled,fillcolor=gold]; 4.394 + 4.395 + subgraph { 4.396 + rank=same; 4.397 + accept [label="Accept",shape=folder,style=filled,fillcolor=cyan]; 4.398 + decline [label="Decline",shape=folder,style=filled,fillcolor=cyan]; 4.399 + } 4.400 + 4.401 + add_to_quota_freebusy [label="Add to quota free/busy",shape=ellipse,style=filled,fillcolor=darkorange]; 4.402 + remove_from_quota_freebusy [label="Remove from quota free/busy",shape=ellipse,style=filled,fillcolor=darkorange]; 4.403 + 4.404 + mail_cadillac -> schedule_across_quota; 4.405 + mail_pontiac -> schedule_across_quota -> schedule -> accept; 4.406 + schedule_across_quota -> decline [style=dashed]; 4.407 + schedule -> add_to_quota_freebusy -> quota_cars -> freebusy_cars_vole; 4.408 + freebusy_cars_vole -> schedule_across_quota; 4.409 + 4.410 + cancel -> remove_from_quota_freebusy -> freebusy_cars_vole; 4.411 +} 4.412 +}}} 4.413 + 4.414 +}}}}
5.1 --- a/imip_store.py Sun Feb 07 23:35:20 2016 +0100 5.2 +++ b/imip_store.py Mon Feb 08 00:14:53 2016 +0100 5.3 @@ -3,7 +3,7 @@ 5.4 """ 5.5 A simple filesystem-based store of calendar data. 5.6 5.7 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 5.8 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 5.9 5.10 This program is free software; you can redistribute it and/or modify it under 5.11 the terms of the GNU General Public License as published by the Free Software 5.12 @@ -20,7 +20,7 @@ 5.13 """ 5.14 5.15 from datetime import datetime 5.16 -from imiptools.config import STORE_DIR, PUBLISH_DIR 5.17 +from imiptools.config import STORE_DIR, PUBLISH_DIR, JOURNAL_DIR 5.18 from imiptools.data import make_calendar, parse_object, to_stream 5.19 from imiptools.dates import format_datetime, get_datetime, to_timezone 5.20 from imiptools.filesys import fix_permissions, FileBase 5.21 @@ -30,12 +30,9 @@ 5.22 from time import sleep 5.23 import codecs 5.24 5.25 -class FileStore(FileBase): 5.26 +class FileStoreBase(FileBase): 5.27 5.28 - "A file store of tabular free/busy data and objects." 5.29 - 5.30 - def __init__(self, store_dir=None): 5.31 - FileBase.__init__(self, store_dir or STORE_DIR) 5.32 + "A file store supporting user-specific locking and tabular data." 5.33 5.34 def acquire_lock(self, user, timeout=None): 5.35 FileBase.acquire_lock(self, timeout, user) 5.36 @@ -137,6 +134,13 @@ 5.37 finally: 5.38 self.release_lock(user) 5.39 5.40 +class FileStore(FileStoreBase): 5.41 + 5.42 + "A file store of tabular free/busy data and objects." 5.43 + 5.44 + def __init__(self, store_dir=None): 5.45 + FileBase.__init__(self, store_dir or STORE_DIR) 5.46 + 5.47 # Store object access. 5.48 5.49 def _get_object(self, user, filename): 5.50 @@ -937,4 +941,90 @@ 5.51 5.52 return True 5.53 5.54 +class FileJournal(FileStoreBase): 5.55 + 5.56 + "A journal system to support quotas." 5.57 + 5.58 + def __init__(self, store_dir=None): 5.59 + FileBase.__init__(self, store_dir or JOURNAL_DIR) 5.60 + 5.61 + # Groups of users sharing quotas. 5.62 + 5.63 + def get_groups(self, quota): 5.64 + 5.65 + "Return the identity mappings for the given 'quota' as a dictionary." 5.66 + 5.67 + filename = self.get_object_in_store(quota, "groups") 5.68 + if not filename or not isfile(filename): 5.69 + return {} 5.70 + 5.71 + return dict(self._get_table_atomic(quota, filename)) 5.72 + 5.73 + def get_limits(self, quota): 5.74 + 5.75 + """ 5.76 + Return the limits for the 'quota' as a dictionary mapping identities or 5.77 + groups to durations. 5.78 + """ 5.79 + 5.80 + filename = self.get_object_in_store(quota, "limits") 5.81 + if not filename or not isfile(filename): 5.82 + return None 5.83 + 5.84 + return dict(self._get_table_atomic(quota, filename)) 5.85 + 5.86 + # Free/busy period access. 5.87 + 5.88 + def get_freebusy(self, quota, user, get_table=None): 5.89 + 5.90 + "Get free/busy details for the given 'quota' and 'user'." 5.91 + 5.92 + filename = self.get_object_in_store(quota, "freebusy", user) 5.93 + if not filename or not isfile(filename): 5.94 + return [] 5.95 + else: 5.96 + return map(lambda t: FreeBusyPeriod(*t), 5.97 + (get_table or self._get_table_atomic)(quota, filename, [(4, None)])) 5.98 + 5.99 + def set_freebusy(self, quota, user, freebusy, set_table=None): 5.100 + 5.101 + "For the given 'quota' and 'user', set 'freebusy' details." 5.102 + 5.103 + filename = self.get_object_in_store(quota, "freebusy", user) 5.104 + if not filename: 5.105 + return False 5.106 + 5.107 + (set_table or self._set_table_atomic)(quota, filename, 5.108 + map(lambda fb: fb.as_tuple(strings_only=True), freebusy)) 5.109 + return True 5.110 + 5.111 + # Journal entry methods. 5.112 + 5.113 + def get_entries(self, quota, group): 5.114 + 5.115 + """ 5.116 + Return a list of journal entries for the given 'quota' for the indicated 5.117 + 'group'. 5.118 + """ 5.119 + 5.120 + filename = self.get_object_in_store(quota, "journal", group) 5.121 + if not filename or not isfile(filename): 5.122 + return [] 5.123 + 5.124 + return self._get_table_atomic(quota, filename, [(1, None)]) 5.125 + 5.126 + def set_entries(self, quota, group, entries): 5.127 + 5.128 + """ 5.129 + For the given 'quota' and indicated 'group', set the list of journal 5.130 + 'entries'. 5.131 + """ 5.132 + 5.133 + filename = self.get_object_in_store(quota, "journal", group) 5.134 + if not filename: 5.135 + return False 5.136 + 5.137 + self._set_table_atomic(quota, filename, entries, [(1, "")]) 5.138 + return True 5.139 + 5.140 # vim: tabstop=4 expandtab shiftwidth=4
6.1 --- a/imiptools/__init__.py Sun Feb 07 23:35:20 2016 +0100 6.2 +++ b/imiptools/__init__.py Mon Feb 08 00:14:53 2016 +0100 6.3 @@ -3,7 +3,7 @@ 6.4 """ 6.5 A processing framework for iMIP content. 6.6 6.7 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 6.8 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 6.9 6.10 This program is free software; you can redistribute it and/or modify it under 6.11 the terms of the GNU General Public License as published by the Free Software 6.12 @@ -58,6 +58,7 @@ 6.13 self.lmtp_socket = None 6.14 self.store_dir = None 6.15 self.publishing_dir = None 6.16 + self.journal_dir = None 6.17 self.preferences_dir = None 6.18 self.debug = False 6.19 6.20 @@ -67,6 +68,9 @@ 6.21 def get_publisher(self): 6.22 return self.publishing_dir and imip_store.FilePublisher(self.publishing_dir) or None 6.23 6.24 + def get_journal(self): 6.25 + return imip_store.FileJournal(self.journal_dir) 6.26 + 6.27 def process(self, f, original_recipients): 6.28 6.29 """ 6.30 @@ -80,6 +84,7 @@ 6.31 messenger = self.messenger 6.32 store = self.get_store() 6.33 publisher = self.get_publisher() 6.34 + journal = self.get_journal() 6.35 preferences_dir = self.preferences_dir 6.36 6.37 # Handle messages with iTIP parts. 6.38 @@ -89,7 +94,7 @@ 6.39 if not self.outgoing_only: 6.40 original_recipients = original_recipients or get_addresses(get_all_values(msg, "To") or []) 6.41 for recipient in original_recipients: 6.42 - Recipient(get_uri(recipient), messenger, store, publisher, preferences_dir, self.handlers, self.outgoing_only, self.debug 6.43 + Recipient(get_uri(recipient), messenger, store, publisher, journal, preferences_dir, self.handlers, self.outgoing_only, self.debug 6.44 ).process(msg, senders) 6.45 6.46 # However, outgoing messages do not usually presume anything about the 6.47 @@ -101,7 +106,7 @@ 6.48 6.49 else: 6.50 senders = [sender for sender in get_addresses(get_all_values(msg, "From") or []) if sender != config.MESSAGE_SENDER] 6.51 - Recipient(senders and senders[0] or None, messenger, store, publisher, preferences_dir, self.handlers, self.outgoing_only, self.debug 6.52 + Recipient(senders and senders[0] or None, messenger, store, publisher, journal, preferences_dir, self.handlers, self.outgoing_only, self.debug 6.53 ).process(msg, senders) 6.54 6.55 def process_args(self, args, stream): 6.56 @@ -120,6 +125,7 @@ 6.57 store_dir = [] 6.58 publishing_dir = [] 6.59 preferences_dir = [] 6.60 + journal_dir = [] 6.61 local_smtp = False 6.62 6.63 l = [] 6.64 @@ -161,6 +167,11 @@ 6.65 elif arg == "-p": 6.66 l = preferences_dir 6.67 6.68 + # Switch to getting the journal directory. 6.69 + 6.70 + elif arg == "-j": 6.71 + l = journal_dir 6.72 + 6.73 # Ignore debugging options. 6.74 6.75 elif arg == "-d": 6.76 @@ -172,6 +183,7 @@ 6.77 self.store_dir = store_dir and store_dir[0] or None 6.78 self.publishing_dir = publishing_dir and publishing_dir[0] or None 6.79 self.preferences_dir = preferences_dir and preferences_dir[0] or None 6.80 + self.journal_dir = journal_dir and journal_dir[0] or None 6.81 self.process(stream, original_recipients) 6.82 6.83 def __call__(self): 6.84 @@ -187,7 +199,7 @@ 6.85 print >>sys.stderr, """\ 6.86 Usage: %s [ -o <recipient> ... ] [-s <sender> ... ] [ -l <socket> | -L ] \\ 6.87 [ -S <store directory> ] [ -P <publishing directory> ] \\ 6.88 - [ -p <preferences directory> ] [ -d ] 6.89 + [ -p <preferences directory> ] [ -j <journal directory> ] [ -d ] 6.90 6.91 Address options: 6.92 6.93 @@ -208,10 +220,11 @@ 6.94 6.95 Configuration options: 6.96 6.97 +-j Indicates the location of quota-related journal information 6.98 +-P Indicates the location of published free/busy resources 6.99 +-p Indicates the location of user preference directories 6.100 -S Indicates the location of the calendar data store containing user storage 6.101 directories 6.102 --P Indicates the location of published free/busy resources 6.103 --p Indicates the location of user preference directories 6.104 6.105 Output options: 6.106 6.107 @@ -244,15 +257,16 @@ 6.108 6.109 "A processor acting as a client on behalf of a recipient." 6.110 6.111 - def __init__(self, user, messenger, store, publisher, preferences_dir, handlers, outgoing_only, debug): 6.112 + def __init__(self, user, messenger, store, publisher, journal, preferences_dir, 6.113 + handlers, outgoing_only, debug): 6.114 6.115 """ 6.116 Initialise the recipient with the given 'user' identity, 'messenger', 6.117 - 'store', 'publisher', 'preferences_dir', 'handlers', 'outgoing_only' and 6.118 - 'debug' status. 6.119 + 'store', 'publisher', 'journal', 'preferences_dir', 'handlers', 6.120 + 'outgoing_only' and 'debug' status. 6.121 """ 6.122 6.123 - Client.__init__(self, user, messenger, store, publisher, preferences_dir) 6.124 + Client.__init__(self, user, messenger, store, publisher, journal, preferences_dir) 6.125 self.handlers = handlers 6.126 self.outgoing_only = outgoing_only 6.127 self.debug = debug 6.128 @@ -269,7 +283,7 @@ 6.129 6.130 handlers = dict([(name, cls(senders, self.user and get_address(self.user), 6.131 self.messenger, self.store, self.publisher, 6.132 - self.preferences_dir)) 6.133 + self.journal, self.preferences_dir)) 6.134 for name, cls in self.handlers]) 6.135 handled = False 6.136
7.1 --- a/imiptools/client.py Sun Feb 07 23:35:20 2016 +0100 7.2 +++ b/imiptools/client.py Mon Feb 08 00:14:53 2016 +0100 7.3 @@ -3,7 +3,7 @@ 7.4 """ 7.5 Common calendar client utilities. 7.6 7.7 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 7.8 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 7.9 7.10 This program is free software; you can redistribute it and/or modify it under 7.11 the terms of the GNU General Public License as published by the Free Software 7.12 @@ -40,17 +40,19 @@ 7.13 default_window_size = 100 7.14 organiser_methods = "ADD", "CANCEL", "DECLINECOUNTER", "PUBLISH", "REQUEST" 7.15 7.16 - def __init__(self, user, messenger=None, store=None, publisher=None, preferences_dir=None): 7.17 + def __init__(self, user, messenger=None, store=None, publisher=None, journal=None, 7.18 + preferences_dir=None): 7.19 7.20 """ 7.21 Initialise a calendar client with the current 'user', plus any 7.22 - 'messenger', 'store' and 'publisher' objects, indicating any specific 7.23 - 'preferences_dir'. 7.24 + 'messenger', 'store', 'publisher' and 'journal' objects, indicating any 7.25 + specific 'preferences_dir'. 7.26 """ 7.27 7.28 self.user = user 7.29 self.messenger = messenger 7.30 self.store = store or imip_store.FileStore() 7.31 + self.journal = journal or imip_store.FileJournal() 7.32 7.33 try: 7.34 self.publisher = publisher or imip_store.FilePublisher() 7.35 @@ -71,6 +73,9 @@ 7.36 def get_publisher(self): 7.37 return self.publisher 7.38 7.39 + def get_journal(self): 7.40 + return self.journal 7.41 + 7.42 # Store-related methods. 7.43 7.44 def acquire_lock(self): 7.45 @@ -213,23 +218,6 @@ 7.46 7.47 # Common operations on calendar data. 7.48 7.49 - def update_senders(self, obj=None): 7.50 - 7.51 - """ 7.52 - Update sender details in 'obj', or the current object if not indicated, 7.53 - removing SENT-BY attributes for attendees other than the current user if 7.54 - those attributes give the URI of the calendar system. 7.55 - """ 7.56 - 7.57 - obj = obj or self.obj 7.58 - calendar_uri = self.messenger and get_uri(self.messenger.sender) 7.59 - for attendee, attendee_attr in uri_items(obj.get_items("ATTENDEE")): 7.60 - if attendee != self.user: 7.61 - if attendee_attr.get("SENT-BY") == calendar_uri: 7.62 - del attendee_attr["SENT-BY"] 7.63 - else: 7.64 - attendee_attr["SENT-BY"] = calendar_uri 7.65 - 7.66 def update_sender(self, attr): 7.67 7.68 "Update the SENT-BY attribute of the 'attr' sender metadata." 7.69 @@ -237,21 +225,6 @@ 7.70 if self.messenger and self.messenger.sender != get_address(self.user): 7.71 attr["SENT-BY"] = get_uri(self.messenger.sender) 7.72 7.73 - def get_sending_attendee(self): 7.74 - 7.75 - "Return the attendee who sent the current object." 7.76 - 7.77 - # Search for the sender of the message or the calendar system address. 7.78 - 7.79 - senders = self.senders or self.messenger and [self.messenger.sender] or [] 7.80 - 7.81 - for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")): 7.82 - if get_address(attendee) in senders or \ 7.83 - get_address(attendee_attr.get("SENT-BY")) in senders: 7.84 - return get_uri(attendee) 7.85 - 7.86 - return None 7.87 - 7.88 def get_periods(self, obj, explicit_only=False): 7.89 7.90 """ 7.91 @@ -371,31 +344,13 @@ 7.92 7.93 return methods, responses 7.94 7.95 - def get_unscheduled_parts(self, periods): 7.96 - 7.97 - "Return message parts describing unscheduled 'periods'." 7.98 - 7.99 - unscheduled_parts = [] 7.100 - 7.101 - if periods: 7.102 - obj = self.obj.copy() 7.103 - obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"]) 7.104 - 7.105 - for p in periods: 7.106 - if not p.origin: 7.107 - continue 7.108 - obj["RECURRENCE-ID"] = obj["DTSTART"] = [(format_datetime(p.get_start()), p.get_start_attr())] 7.109 - obj["DTEND"] = [(format_datetime(p.get_end()), p.get_end_attr())] 7.110 - unscheduled_parts.append(obj.to_part("CANCEL")) 7.111 - 7.112 - return unscheduled_parts 7.113 - 7.114 class ClientForObject(Client): 7.115 7.116 "A client maintaining a specific object." 7.117 7.118 - def __init__(self, obj, user, messenger=None, store=None, publisher=None, preferences_dir=None): 7.119 - Client.__init__(self, user, messenger, store, publisher, preferences_dir) 7.120 + def __init__(self, obj, user, messenger=None, store=None, publisher=None, 7.121 + journal=None, preferences_dir=None): 7.122 + Client.__init__(self, user, messenger, store, publisher, journal, preferences_dir) 7.123 self.set_object(obj) 7.124 7.125 def set_object(self, obj): 7.126 @@ -433,6 +388,59 @@ 7.127 7.128 return get_uri(self.obj.get_value("ORGANIZER")) == self.user 7.129 7.130 + # Common operations on calendar data. 7.131 + 7.132 + def update_senders(self, obj=None): 7.133 + 7.134 + """ 7.135 + Update sender details in 'obj', or the current object if not indicated, 7.136 + removing SENT-BY attributes for attendees other than the current user if 7.137 + those attributes give the URI of the calendar system. 7.138 + """ 7.139 + 7.140 + obj = obj or self.obj 7.141 + calendar_uri = self.messenger and get_uri(self.messenger.sender) 7.142 + for attendee, attendee_attr in uri_items(obj.get_items("ATTENDEE")): 7.143 + if attendee != self.user: 7.144 + if attendee_attr.get("SENT-BY") == calendar_uri: 7.145 + del attendee_attr["SENT-BY"] 7.146 + else: 7.147 + attendee_attr["SENT-BY"] = calendar_uri 7.148 + 7.149 + def get_sending_attendee(self): 7.150 + 7.151 + "Return the attendee who sent the current object." 7.152 + 7.153 + # Search for the sender of the message or the calendar system address. 7.154 + 7.155 + senders = self.senders or self.messenger and [self.messenger.sender] or [] 7.156 + 7.157 + for attendee, attendee_attr in uri_items(self.obj.get_items("ATTENDEE")): 7.158 + if get_address(attendee) in senders or \ 7.159 + get_address(attendee_attr.get("SENT-BY")) in senders: 7.160 + return get_uri(attendee) 7.161 + 7.162 + return None 7.163 + 7.164 + def get_unscheduled_parts(self, periods): 7.165 + 7.166 + "Return message parts describing unscheduled 'periods'." 7.167 + 7.168 + unscheduled_parts = [] 7.169 + 7.170 + if periods: 7.171 + obj = self.obj.copy() 7.172 + obj.remove_all(["RRULE", "RDATE", "DTSTART", "DTEND", "DURATION"]) 7.173 + 7.174 + for p in periods: 7.175 + if not p.origin: 7.176 + continue 7.177 + obj["RECURRENCE-ID"] = obj["DTSTART"] = [(format_datetime(p.get_start()), p.get_start_attr())] 7.178 + obj["DTEND"] = [(format_datetime(p.get_end()), p.get_end_attr())] 7.179 + unscheduled_parts.append(obj.to_part("CANCEL")) 7.180 + 7.181 + return unscheduled_parts 7.182 + 7.183 # Object update methods. 7.184 7.185 def update_recurrenceid(self):
8.1 --- a/imiptools/config.py Sun Feb 07 23:35:20 2016 +0100 8.2 +++ b/imiptools/config.py Mon Feb 08 00:14:53 2016 +0100 8.3 @@ -26,6 +26,10 @@ 8.4 8.5 PREFERENCES_DIR = "/var/lib/imip-agent/preferences" 8.6 8.7 +# The location of quota-related journal information. 8.8 + 8.9 +JOURNAL_DIR = "/var/lib/imip-agent/journal" 8.10 + 8.11 # Permissions for files. 8.12 # This is meant to ensure that both the agent and Web users can access files. 8.13
9.1 --- a/imiptools/handlers/__init__.py Sun Feb 07 23:35:20 2016 +0100 9.2 +++ b/imiptools/handlers/__init__.py Mon Feb 08 00:14:53 2016 +0100 9.3 @@ -3,7 +3,7 @@ 9.4 """ 9.5 General handler support for incoming calendar objects. 9.6 9.7 -Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk> 9.8 +Copyright (C) 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk> 9.9 9.10 This program is free software; you can redistribute it and/or modify it under 9.11 the terms of the GNU General Public License as published by the Free Software 9.12 @@ -44,7 +44,7 @@ 9.13 "General handler support." 9.14 9.15 def __init__(self, senders=None, recipient=None, messenger=None, store=None, 9.16 - publisher=None, preferences_dir=None): 9.17 + publisher=None, journal=None, preferences_dir=None): 9.18 9.19 """ 9.20 Initialise the handler with any specifically indicated 'senders' and 9.21 @@ -53,11 +53,12 @@ 9.22 The optional 'messenger' provides a means of interacting with the mail 9.23 system. 9.24 9.25 - The optional 'store' and 'publisher' can be specified to override the 9.26 - default store and publisher objects. 9.27 + The optional 'store', 'publisher' and 'journal' can be specified to 9.28 + override the default store and publisher objects. 9.29 """ 9.30 9.31 - ClientForObject.__init__(self, None, recipient and get_uri(recipient), messenger, store, publisher, preferences_dir) 9.32 + ClientForObject.__init__(self, None, recipient and get_uri(recipient), 9.33 + messenger, store, publisher, journal, preferences_dir) 9.34 9.35 self.senders = senders and set(map(get_address, senders)) 9.36 self.recipient = recipient and get_address(recipient)
10.1 --- a/imiptools/handlers/resource.py Sun Feb 07 23:35:20 2016 +0100 10.2 +++ b/imiptools/handlers/resource.py Mon Feb 08 00:14:53 2016 +0100 10.3 @@ -22,7 +22,8 @@ 10.4 from imiptools.data import get_address, to_part, uri_dict 10.5 from imiptools.handlers import Handler 10.6 from imiptools.handlers.common import CommonFreebusy, CommonEvent 10.7 -from imiptools.handlers.scheduling import apply_scheduling_functions 10.8 +from imiptools.handlers.scheduling import apply_scheduling_functions, \ 10.9 + confirm_scheduling, retract_scheduling 10.10 10.11 class ResourceHandler(CommonEvent, Handler): 10.12 10.13 @@ -77,6 +78,10 @@ 10.14 10.15 self.update_event_in_freebusy(for_organiser=False) 10.16 10.17 + # Confirm the scheduling of the recurrence. 10.18 + 10.19 + self.confirm_scheduling() 10.20 + 10.21 def _schedule_for_attendee(self): 10.22 10.23 "Attempt to schedule the current object for the current user." 10.24 @@ -108,6 +113,11 @@ 10.25 else: 10.26 self.store.remove_cancellation(self.user, self.uid, self.recurrenceid) 10.27 10.28 + # Confirm any scheduling. 10.29 + 10.30 + if scheduled == "ACCEPTED": 10.31 + self.confirm_scheduling() 10.32 + 10.33 # For countered proposals, record the offer in the resource's 10.34 # free/busy collection. 10.35 10.36 @@ -145,6 +155,10 @@ 10.37 self.store.set_event(self.user, self.uid, self.recurrenceid, self.obj.to_node()) 10.38 self.store.cancel_event(self.user, self.uid, self.recurrenceid) 10.39 10.40 + # Retract the scheduling of the event. 10.41 + 10.42 + self.retract_scheduling() 10.43 + 10.44 def _revoke_for_attendee(self): 10.45 10.46 "Revoke any counter-proposal recorded as a free/busy offer." 10.47 @@ -169,6 +183,24 @@ 10.48 10.49 return apply_scheduling_functions(functions, self) 10.50 10.51 + def confirm_scheduling(self): 10.52 + 10.53 + "Confirm that this event has been scheduled." 10.54 + 10.55 + functions = self.get_preferences().get("confirmation_function") 10.56 + 10.57 + if functions: 10.58 + confirm_scheduling(functions.split("\n"), self) 10.59 + 10.60 + def retract_scheduling(self): 10.61 + 10.62 + "Retract this event from scheduling records." 10.63 + 10.64 + functions = self.get_preferences().get("retraction_function") 10.65 + 10.66 + if functions: 10.67 + retract_scheduling(functions.split("\n"), self) 10.68 + 10.69 class Event(ResourceHandler): 10.70 10.71 "An event handler."
11.1 --- a/imiptools/handlers/scheduling/__init__.py Sun Feb 07 23:35:20 2016 +0100 11.2 +++ b/imiptools/handlers/scheduling/__init__.py Mon Feb 08 00:14:53 2016 +0100 11.3 @@ -20,8 +20,11 @@ 11.4 """ 11.5 11.6 from imiptools.text import parse_line 11.7 -from imiptools.handlers.scheduling.manifest import scheduling_functions 11.8 -import re 11.9 +from imiptools.handlers.scheduling.manifest import confirmation_functions, \ 11.10 + retraction_functions, \ 11.11 + scheduling_functions 11.12 + 11.13 +# Function application/invocation. 11.14 11.15 def apply_scheduling_functions(functions, handler): 11.16 11.17 @@ -32,7 +35,7 @@ 11.18 11.19 # Obtain the actual scheduling functions with arguments. 11.20 11.21 - functions = get_scheduling_function_calls(functions) 11.22 + functions = get_function_calls(functions, scheduling_functions) 11.23 11.24 response = "ACCEPTED" 11.25 11.26 @@ -62,7 +65,49 @@ 11.27 11.28 return response 11.29 11.30 -def get_scheduling_function_calls(lines): 11.31 +def confirm_scheduling(functions, handler): 11.32 + 11.33 + """ 11.34 + Confirm scheduling using the given listener 'functions' for the current 11.35 + object of the given 'handler'. 11.36 + """ 11.37 + 11.38 + # Obtain the actual listener functions with arguments. 11.39 + 11.40 + functions = get_function_calls(functions, confirmation_functions) 11.41 + apply_functions(functions, handler) 11.42 + 11.43 +def retract_scheduling(functions, handler): 11.44 + 11.45 + """ 11.46 + Retract scheduling using the given listener 'functions' for the current 11.47 + object of the given 'handler'. 11.48 + """ 11.49 + 11.50 + # Obtain the actual listener functions with arguments. 11.51 + 11.52 + functions = get_function_calls(functions, retraction_functions) 11.53 + apply_functions(functions, handler) 11.54 + 11.55 +def apply_functions(functions, handler): 11.56 + 11.57 + """ 11.58 + Apply the given notification 'functions' for the current object of the given 11.59 + 'handler'. 11.60 + """ 11.61 + 11.62 + for fn, args in functions: 11.63 + 11.64 + # NOTE: Should signal an error for incorrectly configured resources. 11.65 + 11.66 + if not fn: 11.67 + continue 11.68 + 11.69 + fn(handler, args) 11.70 + 11.71 +# Function lookup. 11.72 + 11.73 +def get_function_calls(lines, registry): 11.74 11.75 """ 11.76 Parse the given 'lines', returning a list of (function, arguments) tuples, 11.77 @@ -72,13 +117,16 @@ 11.78 Each of the 'lines' should employ the function name and argument strings 11.79 separated by whitespace, with any whitespace inside arguments quoted using 11.80 single or double quotes. 11.81 + 11.82 + The given 'registry' indicates the mapping from function names to actual 11.83 + functions. 11.84 """ 11.85 11.86 functions = [] 11.87 11.88 for line in lines: 11.89 parts = parse_line(line) 11.90 - functions.append((scheduling_functions.get(parts[0]), parts[1:])) 11.91 + functions.append((registry.get(parts[0]), parts[1:])) 11.92 11.93 return functions 11.94
12.1 --- a/imiptools/handlers/scheduling/access.py Sun Feb 07 23:35:20 2016 +0100 12.2 +++ b/imiptools/handlers/scheduling/access.py Mon Feb 08 00:14:53 2016 +0100 12.3 @@ -132,4 +132,9 @@ 12.4 "same_domain_only" : same_domain_only, 12.5 } 12.6 12.7 +# Registries of listener functions. 12.8 + 12.9 +confirmation_functions = {} 12.10 +retraction_functions = {} 12.11 + 12.12 # vim: tabstop=4 expandtab shiftwidth=4
13.1 --- a/imiptools/handlers/scheduling/freebusy.py Sun Feb 07 23:35:20 2016 +0100 13.2 +++ b/imiptools/handlers/scheduling/freebusy.py Mon Feb 08 00:14:53 2016 +0100 13.3 @@ -42,8 +42,8 @@ 13.4 13.5 periods = handler.get_periods(handler.obj) 13.6 13.7 - freebusy = freebusy or handler.store.get_freebusy(handler.user) 13.8 - offers = handler.store.get_freebusy_offers(handler.user) 13.9 + freebusy = freebusy or handler.get_store().get_freebusy(handler.user) 13.10 + offers = handler.get_store().get_freebusy_offers(handler.user) 13.11 13.12 # Check the periods against any scheduled events and against 13.13 # any outstanding offers. 13.14 @@ -105,7 +105,7 @@ 13.15 13.16 # There should already be free/busy information for the user. 13.17 13.18 - user_freebusy = handler.store.get_freebusy(handler.user) 13.19 + user_freebusy = handler.get_store().get_freebusy(handler.user) 13.20 busy = user_freebusy 13.21 13.22 # Subtract any periods from this event from the free/busy collections. 13.23 @@ -116,7 +116,7 @@ 13.24 13.25 for attendee in uri_values(handler.obj.get_values("ATTENDEE")): 13.26 if attendee != handler.user: 13.27 - freebusy = handler.store.get_freebusy_for_other(handler.user, attendee) 13.28 + freebusy = handler.get_store().get_freebusy_for_other(handler.user, attendee) 13.29 if freebusy: 13.30 remove_periods(freebusy, event_periods) 13.31 busy += freebusy 13.32 @@ -217,4 +217,9 @@ 13.33 "schedule_next_available_in_freebusy" : schedule_next_available_in_freebusy, 13.34 } 13.35 13.36 +# Registries of listener functions. 13.37 + 13.38 +confirmation_functions = {} 13.39 +retraction_functions = {} 13.40 + 13.41 # vim: tabstop=4 expandtab shiftwidth=4
14.1 --- a/imiptools/handlers/scheduling/manifest.py Sun Feb 07 23:35:20 2016 +0100 14.2 +++ b/imiptools/handlers/scheduling/manifest.py Mon Feb 08 00:14:53 2016 +0100 14.3 @@ -1,5 +1,31 @@ 14.4 +confirmation_functions = {} 14.5 +retraction_functions = {} 14.6 scheduling_functions = {} 14.7 -from imiptools.handlers.scheduling.freebusy import scheduling_functions as l 14.8 -scheduling_functions.update(l) 14.9 -from imiptools.handlers.scheduling.access import scheduling_functions as l 14.10 -scheduling_functions.update(l) 14.11 + 14.12 +from imiptools.handlers.scheduling.quota import ( 14.13 + confirmation_functions as c, 14.14 + retraction_functions as r, 14.15 + scheduling_functions as s) 14.16 + 14.17 +confirmation_functions.update(c) 14.18 +retraction_functions.update(r) 14.19 +scheduling_functions.update(s) 14.20 + 14.21 +from imiptools.handlers.scheduling.freebusy import ( 14.22 + confirmation_functions as c, 14.23 + retraction_functions as r, 14.24 + scheduling_functions as s) 14.25 + 14.26 +confirmation_functions.update(c) 14.27 +retraction_functions.update(r) 14.28 +scheduling_functions.update(s) 14.29 + 14.30 +from imiptools.handlers.scheduling.access import ( 14.31 + confirmation_functions as c, 14.32 + retraction_functions as r, 14.33 + scheduling_functions as s) 14.34 + 14.35 +confirmation_functions.update(c) 14.36 +retraction_functions.update(r) 14.37 +scheduling_functions.update(s) 14.38 +
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/imiptools/handlers/scheduling/quota.py Mon Feb 08 00:14:53 2016 +0100 15.3 @@ -0,0 +1,322 @@ 15.4 +#!/usr/bin/env python 15.5 + 15.6 +""" 15.7 +Quota-related scheduling functionality. 15.8 + 15.9 +Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk> 15.10 + 15.11 +This program is free software; you can redistribute it and/or modify it under 15.12 +the terms of the GNU General Public License as published by the Free Software 15.13 +Foundation; either version 3 of the License, or (at your option) any later 15.14 +version. 15.15 + 15.16 +This program is distributed in the hope that it will be useful, but WITHOUT 15.17 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15.18 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 15.19 +details. 15.20 + 15.21 +You should have received a copy of the GNU General Public License along with 15.22 +this program. If not, see <http://www.gnu.org/licenses/>. 15.23 +""" 15.24 + 15.25 +from imiptools.dates import format_duration, get_duration 15.26 +from imiptools.data import get_uri 15.27 +from imiptools.period import Endless 15.28 +from datetime import timedelta 15.29 + 15.30 +# Quota maintenance. 15.31 + 15.32 +def check_quota(handler, args): 15.33 + 15.34 + """ 15.35 + Check the current object of the given 'handler' against the applicable 15.36 + quota. 15.37 + """ 15.38 + 15.39 + quota, group = _get_quota_and_group(handler, args) 15.40 + 15.41 + # Obtain the journal entries and check the balance. 15.42 + 15.43 + journal = handler.get_journal() 15.44 + entries = journal.get_entries(quota, group) 15.45 + limits = journal.get_limits(quota) 15.46 + 15.47 + # Obtain a limit for the group or any general limit. 15.48 + # Decline invitations if no limit has been set. 15.49 + 15.50 + limit = limits.get(group) or limits.get("*") 15.51 + if not limit: 15.52 + return "DECLINED" 15.53 + 15.54 + # Decline events whose durations exceed the balance. 15.55 + 15.56 + total = _get_duration(handler) 15.57 + 15.58 + if total == Endless(): 15.59 + return "DECLINED" 15.60 + 15.61 + balance = get_duration(limit) - _get_usage(entries) 15.62 + 15.63 + if total > balance: 15.64 + return "DECLINED" 15.65 + else: 15.66 + return "ACCEPTED" 15.67 + 15.68 +def add_to_quota(handler, args): 15.69 + 15.70 + """ 15.71 + Record details of the current object of the given 'handler' in the 15.72 + applicable quota. 15.73 + """ 15.74 + 15.75 + quota, group = _get_quota_and_group(handler, args) 15.76 + 15.77 + total = _get_duration(handler) 15.78 + 15.79 + # Obtain the journal entries and limits. 15.80 + 15.81 + journal = handler.get_journal() 15.82 + journal.acquire_lock(quota) 15.83 + 15.84 + try: 15.85 + entries = journal.get_entries(quota, group) 15.86 + if _add_to_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): 15.87 + journal.set_entries(quota, group, entries) 15.88 + 15.89 + finally: 15.90 + journal.release_lock(quota) 15.91 + 15.92 +def remove_from_quota(handler, args): 15.93 + 15.94 + """ 15.95 + Remove details of the current object of the given 'handler' from the 15.96 + applicable quota. 15.97 + """ 15.98 + 15.99 + quota, group = _get_quota_and_group(handler, args) 15.100 + 15.101 + total = _get_duration(handler) 15.102 + 15.103 + # Obtain the journal entries and limits. 15.104 + 15.105 + journal = handler.get_journal() 15.106 + journal.acquire_lock(quota) 15.107 + 15.108 + try: 15.109 + entries = journal.get_entries(quota, group) 15.110 + if _remove_from_entries(entries, handler.obj.get_uid(), handler.obj.get_recurrenceid(), format_duration(total)): 15.111 + journal.set_entries(quota, group, entries) 15.112 + 15.113 + finally: 15.114 + journal.release_lock(quota) 15.115 + 15.116 +def _get_quota_and_group(handler, args): 15.117 + 15.118 + """ 15.119 + Combine information about the current object from the 'handler' with the 15.120 + given 'args' to return a tuple containing the quota group and the user 15.121 + identity or group involved. 15.122 + """ 15.123 + 15.124 + quota = args and args[0] or handler.user 15.125 + 15.126 + # Obtain the identity to whom the quota will apply. 15.127 + 15.128 + organiser = get_uri(handler.obj.get_value("ORGANIZER")) 15.129 + 15.130 + # Obtain any user group to which the quota will apply instead. 15.131 + 15.132 + journal = handler.get_journal() 15.133 + groups = journal.get_groups(quota) 15.134 + 15.135 + return quota, groups.get(organiser) or organiser 15.136 + 15.137 +def _get_duration(handler): 15.138 + 15.139 + "Return the duration of the current object provided by the 'handler'." 15.140 + 15.141 + # Count only explicit periods. 15.142 + # NOTE: Should reject indefinitely recurring events. 15.143 + 15.144 + total = timedelta(0) 15.145 + 15.146 + for period in handler.get_periods(handler.obj, explicit_only=True): 15.147 + duration = period.get_duration() 15.148 + 15.149 + # Decline events whose period durations are endless. 15.150 + 15.151 + if duration == Endless(): 15.152 + return duration 15.153 + else: 15.154 + total += duration 15.155 + 15.156 + return total 15.157 + 15.158 +def _get_usage(entries): 15.159 + 15.160 + "Return the usage total according to the given 'entries'." 15.161 + 15.162 + total = timedelta(0) 15.163 + 15.164 + for found_uid, found_recurrenceid, found_duration in entries: 15.165 + retraction = found_duration.startswith("-") 15.166 + multiplier = retraction and -1 or 1 15.167 + total += multiplier * get_duration(found_duration[retraction and 1 or 0:]) 15.168 + 15.169 + return total 15.170 + 15.171 +def _add_to_entries(entries, uid, recurrenceid, duration): 15.172 + 15.173 + """ 15.174 + Add to 'entries' an entry for the event having the given 'uid' and 15.175 + 'recurrenceid' with the given 'duration'. 15.176 + """ 15.177 + 15.178 + confirmed = _find_applicable_entry(entries, uid, recurrenceid, duration) 15.179 + 15.180 + # Where a previous entry still applies, retract it if different. 15.181 + 15.182 + if confirmed: 15.183 + found_uid, found_recurrenceid, found_duration = confirmed 15.184 + if found_duration != duration: 15.185 + entries.append((found_uid, found_recurrenceid, "-%s" % found_duration)) 15.186 + else: 15.187 + return False 15.188 + 15.189 + # Without an applicable previous entry, add a new entry. 15.190 + 15.191 + entries.append((uid, recurrenceid, duration)) 15.192 + return True 15.193 + 15.194 +def _remove_from_entries(entries, uid, recurrenceid, duration): 15.195 + 15.196 + """ 15.197 + Remove from the given 'entries' any entry for the event having the given 15.198 + 'uid' and 'recurrenceid' with the given 'duration'. 15.199 + """ 15.200 + 15.201 + confirmed = _find_applicable_entry(entries, uid, recurrenceid, duration) 15.202 + 15.203 + # Where a previous entry still applies, retract it. 15.204 + 15.205 + if confirmed: 15.206 + found_uid, found_recurrenceid, found_duration = confirmed 15.207 + entries.append((found_uid, found_recurrenceid, "-%s" % found_duration)) 15.208 + return found_duration == duration 15.209 + 15.210 + return False 15.211 + 15.212 +def _find_applicable_entry(entries, uid, recurrenceid, duration): 15.213 + 15.214 + """ 15.215 + Within 'entries', find any applicable previous entry for this event, 15.216 + using the 'uid', 'recurrenceid' and 'duration'. 15.217 + """ 15.218 + 15.219 + confirmed = None 15.220 + 15.221 + for found_uid, found_recurrenceid, found_duration in entries: 15.222 + if uid == found_uid and recurrenceid == found_recurrenceid: 15.223 + if found_duration.startswith("-"): 15.224 + confirmed = None 15.225 + else: 15.226 + confirmed = found_uid, found_recurrenceid, found_duration 15.227 + 15.228 + return confirmed 15.229 + 15.230 +# Collective free/busy maintenance. 15.231 + 15.232 +def schedule_across_quota(handler, args): 15.233 + 15.234 + """ 15.235 + Check the current object of the given 'handler' against the schedules 15.236 + managed by the quota. 15.237 + """ 15.238 + 15.239 + quota, organiser = _get_quota_and_identity(handler, args) 15.240 + 15.241 + # If newer than any old version, discard old details from the 15.242 + # free/busy record and check for suitability. 15.243 + 15.244 + periods = handler.get_periods(handler.obj) 15.245 + freebusy = handler.get_journal().get_freebusy(quota, organiser) 15.246 + scheduled = handler.can_schedule(freebusy, periods) 15.247 + 15.248 + return scheduled and "ACCEPTED" or "DECLINED" 15.249 + 15.250 +def add_to_quota_freebusy(handler, args): 15.251 + 15.252 + """ 15.253 + Record details of the current object of the 'handler' in the applicable 15.254 + free/busy resource. 15.255 + """ 15.256 + 15.257 + quota, organiser = _get_quota_and_identity(handler, args) 15.258 + 15.259 + journal = handler.get_journal() 15.260 + journal.acquire_lock(quota) 15.261 + 15.262 + try: 15.263 + freebusy = journal.get_freebusy(quota, organiser) 15.264 + handler.update_freebusy(freebusy, organiser, True) 15.265 + journal.set_freebusy(quota, organiser, freebusy) 15.266 + 15.267 + finally: 15.268 + journal.release_lock(quota) 15.269 + 15.270 +def remove_from_quota_freebusy(handler, args): 15.271 + 15.272 + """ 15.273 + Remove details of the current object of the 'handler' from the applicable 15.274 + free/busy resource. 15.275 + """ 15.276 + 15.277 + quota, organiser = _get_quota_and_identity(handler, args) 15.278 + 15.279 + journal = handler.get_journal() 15.280 + journal.acquire_lock(quota) 15.281 + 15.282 + try: 15.283 + freebusy = journal.get_freebusy(quota, organiser) 15.284 + handler.remove_from_freebusy(freebusy) 15.285 + journal.set_freebusy(quota, organiser, freebusy) 15.286 + 15.287 + finally: 15.288 + journal.release_lock(quota) 15.289 + 15.290 +def _get_quota_and_identity(handler, args): 15.291 + 15.292 + """ 15.293 + Combine information about the current object from the 'handler' with the 15.294 + given 'args' to return a tuple containing the quota group and the user 15.295 + identity involved. 15.296 + """ 15.297 + 15.298 + quota = args and args[0] or handler.user 15.299 + 15.300 + # Obtain the identity for whom the scheduling will apply. 15.301 + 15.302 + organiser = get_uri(handler.obj.get_value("ORGANIZER")) 15.303 + 15.304 + return quota, organiser 15.305 + 15.306 +# Registry of scheduling functions. 15.307 + 15.308 +scheduling_functions = { 15.309 + "check_quota" : check_quota, 15.310 + "schedule_across_quota" : schedule_across_quota, 15.311 + } 15.312 + 15.313 +# Registries of listener functions. 15.314 + 15.315 +confirmation_functions = { 15.316 + "add_to_quota" : add_to_quota, 15.317 + "add_to_quota_freebusy" : add_to_quota_freebusy, 15.318 + } 15.319 + 15.320 +retraction_functions = { 15.321 + "remove_from_quota" : remove_from_quota, 15.322 + "remove_from_quota_freebusy" : remove_from_quota_freebusy, 15.323 + } 15.324 + 15.325 +# vim: tabstop=4 expandtab shiftwidth=4
16.1 --- a/tests/common.sh Sun Feb 07 23:35:20 2016 +0100 16.2 +++ b/tests/common.sh Mon Feb 08 00:14:53 2016 +0100 16.3 @@ -6,8 +6,9 @@ 16.4 STORE=/tmp/store 16.5 STATIC=/tmp/static 16.6 PREFS=/tmp/prefs 16.7 +JOURNAL=/tmp/journal 16.8 16.9 -ARGS="-S $STORE -P $STATIC -p $PREFS -d" 16.10 +ARGS="-S $STORE -P $STATIC -p $PREFS -j $JOURNAL -d" 16.11 16.12 ACCEPT_SCRIPT="$THIS_DIR/test_handle.py" 16.13 ACCEPT_ARGS="accept $STORE $PREFS" 16.14 @@ -38,8 +39,9 @@ 16.15 PYTHONPATH="$BASE_DIR" 16.16 export PYTHONPATH 16.17 16.18 -rm -rf $STORE 16.19 -rm -rf $STATIC 16.20 -rm -rf $PREFS 16.21 -rm -f $ERROR 16.22 +rm -rf "$STORE" 16.23 +rm -rf "$STATIC" 16.24 +rm -rf "$PREFS" 16.25 +rm -rf "$JOURNAL" 16.26 +rm -f "$ERROR" 16.27 rm -f out*.tmp
17.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 17.2 +++ b/tests/templates/event-cancel-car.txt Mon Feb 08 00:14:53 2016 +0100 17.3 @@ -0,0 +1,36 @@ 17.4 +Content-Type: multipart/alternative; boundary="===============0047278175==" 17.5 +MIME-Version: 1.0 17.6 +From: paul.boddie@example.com 17.7 +To: resource-car-porsche911@example.com 17.8 +Subject: Cancellation! 17.9 + 17.10 +Cancel the event completely. 17.11 + 17.12 +--===============0047278175== 17.13 +Content-Type: text/plain; charset="us-ascii" 17.14 +MIME-Version: 1.0 17.15 +Content-Transfer-Encoding: 7bit 17.16 + 17.17 +This message contains an event. 17.18 +--===============0047278175== 17.19 +MIME-Version: 1.0 17.20 +Content-Transfer-Encoding: 7bit 17.21 +Content-Type: text/calendar; charset="us-ascii"; method="CANCEL" 17.22 + 17.23 +BEGIN:VCALENDAR 17.24 +PRODID:-//imip-agent/test//EN 17.25 +METHOD:CANCEL 17.26 +VERSION:2.0 17.27 +BEGIN:VEVENT 17.28 +ORGANIZER:mailto:paul.boddie@example.com 17.29 +ATTENDEE:mailto:paul.boddie@example.com 17.30 +ATTENDEE;RSVP=TRUE:mailto:resource-car-porsche911@example.com 17.31 +DTSTAMP:20141125T004600Z 17.32 +DTSTART;TZID=Europe/Oslo:20141126T160000 17.33 +DTEND;TZID=Europe/Oslo:20141126T170000 17.34 +SUMMARY:Test drive 17.35 +UID:event21@example.com 17.36 +END:VEVENT 17.37 +END:VCALENDAR 17.38 + 17.39 +--===============0047278175==--
18.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 18.2 +++ b/tests/templates/event-request-car-conflict.txt Mon Feb 08 00:14:53 2016 +0100 18.3 @@ -0,0 +1,34 @@ 18.4 +Content-Type: multipart/alternative; boundary="===============0047278175==" 18.5 +MIME-Version: 1.0 18.6 +From: paul.boddie@example.com 18.7 +To: resource-car-fiat500@example.com 18.8 +Subject: Invitation! 18.9 + 18.10 +--===============0047278175== 18.11 +Content-Type: text/plain; charset="us-ascii" 18.12 +MIME-Version: 1.0 18.13 +Content-Transfer-Encoding: 7bit 18.14 + 18.15 +This message contains an event. 18.16 +--===============0047278175== 18.17 +MIME-Version: 1.0 18.18 +Content-Transfer-Encoding: 7bit 18.19 +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST" 18.20 + 18.21 +BEGIN:VCALENDAR 18.22 +PRODID:-//imip-agent/test//EN 18.23 +METHOD:REQUEST 18.24 +VERSION:2.0 18.25 +BEGIN:VEVENT 18.26 +ORGANIZER:mailto:paul.boddie@example.com 18.27 +ATTENDEE;ROLE=CHAIR:mailto:paul.boddie@example.com 18.28 +ATTENDEE;RSVP=TRUE:mailto:resource-car-fiat500@example.com 18.29 +DTSTAMP:20141125T004600Z 18.30 +DTSTART;TZID=Europe/Oslo:20141126T163000 18.31 +DTEND;TZID=Europe/Oslo:20141126T173000 18.32 +SUMMARY:Test drive 18.33 +UID:event22@example.com 18.34 +END:VEVENT 18.35 +END:VCALENDAR 18.36 + 18.37 +--===============0047278175==--
19.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 19.2 +++ b/tests/templates/event-request-car-moved.txt Mon Feb 08 00:14:53 2016 +0100 19.3 @@ -0,0 +1,34 @@ 19.4 +Content-Type: multipart/alternative; boundary="===============0047278175==" 19.5 +MIME-Version: 1.0 19.6 +From: paul.boddie@example.com 19.7 +To: resource-car-porsche911@example.com 19.8 +Subject: Invitation! 19.9 + 19.10 +--===============0047278175== 19.11 +Content-Type: text/plain; charset="us-ascii" 19.12 +MIME-Version: 1.0 19.13 +Content-Transfer-Encoding: 7bit 19.14 + 19.15 +This message contains an event. 19.16 +--===============0047278175== 19.17 +MIME-Version: 1.0 19.18 +Content-Transfer-Encoding: 7bit 19.19 +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST" 19.20 + 19.21 +BEGIN:VCALENDAR 19.22 +PRODID:-//imip-agent/test//EN 19.23 +METHOD:REQUEST 19.24 +VERSION:2.0 19.25 +BEGIN:VEVENT 19.26 +ORGANIZER:mailto:paul.boddie@example.com 19.27 +ATTENDEE;ROLE=CHAIR:mailto:paul.boddie@example.com 19.28 +ATTENDEE;RSVP=TRUE:mailto:resource-car-porsche911@example.com 19.29 +DTSTAMP:20141125T004600Z 19.30 +DTSTART;TZID=Europe/Oslo:20141126T153000 19.31 +DTEND;TZID=Europe/Oslo:20141126T163000 19.32 +SUMMARY:Test drive 19.33 +UID:event21@example.com 19.34 +END:VEVENT 19.35 +END:VCALENDAR 19.36 + 19.37 +--===============0047278175==--
20.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 20.2 +++ b/tests/templates/event-request-car.txt Mon Feb 08 00:14:53 2016 +0100 20.3 @@ -0,0 +1,34 @@ 20.4 +Content-Type: multipart/alternative; boundary="===============0047278175==" 20.5 +MIME-Version: 1.0 20.6 +From: paul.boddie@example.com 20.7 +To: resource-car-porsche911@example.com 20.8 +Subject: Invitation! 20.9 + 20.10 +--===============0047278175== 20.11 +Content-Type: text/plain; charset="us-ascii" 20.12 +MIME-Version: 1.0 20.13 +Content-Transfer-Encoding: 7bit 20.14 + 20.15 +This message contains an event. 20.16 +--===============0047278175== 20.17 +MIME-Version: 1.0 20.18 +Content-Transfer-Encoding: 7bit 20.19 +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST" 20.20 + 20.21 +BEGIN:VCALENDAR 20.22 +PRODID:-//imip-agent/test//EN 20.23 +METHOD:REQUEST 20.24 +VERSION:2.0 20.25 +BEGIN:VEVENT 20.26 +ORGANIZER:mailto:paul.boddie@example.com 20.27 +ATTENDEE;ROLE=CHAIR:mailto:paul.boddie@example.com 20.28 +ATTENDEE;RSVP=TRUE:mailto:resource-car-porsche911@example.com 20.29 +DTSTAMP:20141125T004600Z 20.30 +DTSTART;TZID=Europe/Oslo:20141126T160000 20.31 +DTEND;TZID=Europe/Oslo:20141126T170000 20.32 +SUMMARY:Test drive 20.33 +UID:event21@example.com 20.34 +END:VEVENT 20.35 +END:VCALENDAR 20.36 + 20.37 +--===============0047278175==--
21.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 21.2 +++ b/tests/templates/fb-request-car-other.txt Mon Feb 08 00:14:53 2016 +0100 21.3 @@ -0,0 +1,31 @@ 21.4 +Content-Type: multipart/alternative; boundary="===============0945993647==" 21.5 +MIME-Version: 1.0 21.6 +From: paul.boddie@example.com 21.7 +To: resource-car-fiat500@example.com 21.8 + 21.9 +--===============0945993647== 21.10 +Content-Type: text/plain; charset="us-ascii" 21.11 +MIME-Version: 1.0 21.12 +Content-Transfer-Encoding: 7bit 21.13 + 21.14 +This message contains a free/busy request. 21.15 +--===============0945993647== 21.16 +Content-Type: text/calendar; charset="us-ascii"; method="REQUEST" 21.17 +MIME-Version: 1.0 21.18 +Content-Transfer-Encoding: 7bit 21.19 + 21.20 +BEGIN:VCALENDAR 21.21 +PRODID:-//imip-agent/test//EN 21.22 +METHOD:REQUEST 21.23 +VERSION:2.0 21.24 +BEGIN:VFREEBUSY 21.25 +ORGANIZER:mailto:paul.boddie@example.com 21.26 +ATTENDEE:mailto:resource-car-fiat500@example.com 21.27 +DTSTAMP:20141125T164400Z 21.28 +DTSTART:20141126T150000Z 21.29 +DTEND:20141126T180000Z 21.30 +UID:fb6@example.com 21.31 +END:VFREEBUSY 21.32 +END:VCALENDAR 21.33 + 21.34 +--===============0945993647==--
22.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 22.2 +++ b/tests/test_resource_invitation_constraints_quota.sh Mon Feb 08 00:14:53 2016 +0100 22.3 @@ -0,0 +1,305 @@ 22.4 +#!/bin/sh 22.5 + 22.6 +. "`dirname \"$0\"`/common.sh" 22.7 + 22.8 +USER1="mailto:resource-car-porsche911@example.com" 22.9 +USER2="mailto:resource-car-fiat500@example.com" 22.10 +SENDER="mailto:paul.boddie@example.com" 22.11 +FBFILE1="$STORE/$USER1/freebusy" 22.12 +FBFILE2="$STORE/$USER2/freebusy" 22.13 +FBSENDERFILE="$STORE/$SENDER/freebusy" 22.14 +QUOTA=cars 22.15 +JOURNALFILE="$JOURNAL/$QUOTA/journal/$SENDER" 22.16 + 22.17 +mkdir -p "$PREFS/$USER1" 22.18 +echo 'Europe/Oslo' > "$PREFS/$USER1/TZID" 22.19 +echo 'share' > "$PREFS/$USER1/freebusy_sharing" 22.20 +cat > "$PREFS/$USER1/scheduling_function" <<EOF 22.21 +schedule_in_freebusy 22.22 +check_quota $QUOTA 22.23 +EOF 22.24 +cat > "$PREFS/$USER1/confirmation_function" <<EOF 22.25 +add_to_quota $QUOTA 22.26 +EOF 22.27 +cat > "$PREFS/$USER1/retraction_function" <<EOF 22.28 +remove_from_quota $QUOTA 22.29 +EOF 22.30 + 22.31 +mkdir -p "$PREFS/$USER2" 22.32 +echo 'Europe/Oslo' > "$PREFS/$USER2/TZID" 22.33 +echo 'share' > "$PREFS/$USER2/freebusy_sharing" 22.34 +cat > "$PREFS/$USER2/scheduling_function" <<EOF 22.35 +schedule_in_freebusy 22.36 +check_quota $QUOTA 22.37 +EOF 22.38 +cat > "$PREFS/$USER2/confirmation_function" <<EOF 22.39 +add_to_quota $QUOTA 22.40 +EOF 22.41 +cat > "$PREFS/$USER2/retraction_function" <<EOF 22.42 +remove_from_quota $QUOTA 22.43 +EOF 22.44 + 22.45 +mkdir -p "$JOURNAL/$QUOTA" 22.46 +echo '*\tPT1H' > "$JOURNAL/$QUOTA/limits" 22.47 + 22.48 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/fb-request-car.txt" 2>> $ERROR \ 22.49 +| "$SHOWMAIL" \ 22.50 +> out0.tmp 22.51 + 22.52 + grep -q 'METHOD:REPLY' out0.tmp \ 22.53 +&& ! grep -q '^FREEBUSY' out0.tmp \ 22.54 +&& echo "Success" \ 22.55 +|| echo "Failed" 22.56 + 22.57 +# Attempt to schedule an event. 22.58 + 22.59 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR 22.60 + 22.61 + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ 22.62 +&& echo "Success" \ 22.63 +|| echo "Failed" 22.64 + 22.65 +# Present the request to the resource. 22.66 + 22.67 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR \ 22.68 +| tee out1r.tmp \ 22.69 +| "$SHOWMAIL" \ 22.70 +> out1.tmp 22.71 + 22.72 + grep -q 'METHOD:REPLY' out1.tmp \ 22.73 +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out1.tmp \ 22.74 +&& echo "Success" \ 22.75 +|| echo "Failed" 22.76 + 22.77 + [ -e "$FBFILE1" ] \ 22.78 +&& grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ 22.79 +&& echo "Success" \ 22.80 +|| echo "Failed" 22.81 + 22.82 +# Check the quota (event is confirmed). 22.83 + 22.84 + [ -e "$JOURNALFILE" ] \ 22.85 +&& grep -q "event21@example.com" "$JOURNALFILE" \ 22.86 +&& echo "Success" \ 22.87 +|| echo "Failed" 22.88 + 22.89 +# Attempt to schedule another event. 22.90 + 22.91 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR 22.92 + 22.93 + grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBSENDERFILE" \ 22.94 +&& echo "Success" \ 22.95 +|| echo "Failed" 22.96 + 22.97 +# Present the request to the resource. 22.98 + 22.99 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR \ 22.100 +| tee out2r.tmp \ 22.101 +| "$SHOWMAIL" \ 22.102 +> out2.tmp 22.103 + 22.104 + grep -q 'METHOD:REPLY' out2.tmp \ 22.105 +&& grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' out2.tmp \ 22.106 +&& echo "Success" \ 22.107 +|| echo "Failed" 22.108 + 22.109 + ! [ -e "$FBFILE2" ] \ 22.110 +|| ! grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ 22.111 +&& echo "Success" \ 22.112 +|| echo "Failed" 22.113 + 22.114 +# Check the quota (event is not confirmed). 22.115 + 22.116 + [ -e "$JOURNALFILE" ] \ 22.117 +&& grep -q "event21@example.com" "$JOURNALFILE" \ 22.118 +&& ! grep -q "event22@example.com" "$JOURNALFILE" \ 22.119 +&& echo "Success" \ 22.120 +|| echo "Failed" 22.121 + 22.122 +# Increase the quota. 22.123 + 22.124 +echo '*\tPT2H' > "$JOURNAL/$QUOTA/limits" 22.125 + 22.126 +# Attempt to schedule the event again. 22.127 + 22.128 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR 22.129 + 22.130 + grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBSENDERFILE" \ 22.131 +&& echo "Success" \ 22.132 +|| echo "Failed" 22.133 + 22.134 +# Present the request to the resource. 22.135 + 22.136 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR \ 22.137 +| tee out3r.tmp \ 22.138 +| "$SHOWMAIL" \ 22.139 +> out3.tmp 22.140 + 22.141 + grep -q 'METHOD:REPLY' out3.tmp \ 22.142 +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out3.tmp \ 22.143 +&& echo "Success" \ 22.144 +|| echo "Failed" 22.145 + 22.146 + [ -e "$FBFILE2" ] \ 22.147 +&& grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ 22.148 +&& echo "Success" \ 22.149 +|| echo "Failed" 22.150 + 22.151 +# Check the quota (event is confirmed). 22.152 + 22.153 + [ -e "$JOURNALFILE" ] \ 22.154 +&& grep -q "event21@example.com" "$JOURNALFILE" \ 22.155 +&& grep -q "event22@example.com" "$JOURNALFILE" \ 22.156 +&& echo "Success" \ 22.157 +|| echo "Failed" 22.158 + 22.159 +# Cancel the first event. 22.160 + 22.161 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-car.txt" 2>> $ERROR 22.162 + 22.163 + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ 22.164 +&& echo "Success" \ 22.165 +|| echo "Failed" 22.166 + 22.167 +# Present the request to the resource. 22.168 + 22.169 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-cancel-car.txt" 2>> $ERROR \ 22.170 +| tee out4r.tmp \ 22.171 +| "$SHOWMAIL" \ 22.172 +> out4.tmp 22.173 + 22.174 + ! grep -q 'METHOD:REPLY' out4.tmp \ 22.175 +&& echo "Success" \ 22.176 +|| echo "Failed" 22.177 + 22.178 + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ 22.179 +&& echo "Success" \ 22.180 +|| echo "Failed" 22.181 + 22.182 +# Check the quota (event is retracted). 22.183 + 22.184 + [ -e "$JOURNALFILE" ] \ 22.185 +&& [ `grep "event21@example.com" "$JOURNALFILE" | wc -l` = '2' ] \ 22.186 +&& grep -q "event22@example.com" "$JOURNALFILE" \ 22.187 +&& echo "Success" \ 22.188 +|| echo "Failed" 22.189 + 22.190 +# Add collective scheduling tests. 22.191 + 22.192 +cat > "$PREFS/$USER1/scheduling_function" <<EOF 22.193 +schedule_in_freebusy 22.194 +schedule_across_quota $QUOTA 22.195 +check_quota $QUOTA 22.196 +EOF 22.197 +cat > "$PREFS/$USER1/confirmation_function" <<EOF 22.198 +add_to_quota_freebusy $QUOTA 22.199 +add_to_quota $QUOTA 22.200 +EOF 22.201 +cat > "$PREFS/$USER1/retraction_function" <<EOF 22.202 +remove_from_quota_freebusy $QUOTA 22.203 +remove_from_quota $QUOTA 22.204 +EOF 22.205 + 22.206 +cat > "$PREFS/$USER2/scheduling_function" <<EOF 22.207 +schedule_in_freebusy 22.208 +schedule_across_quota $QUOTA 22.209 +check_quota $QUOTA 22.210 +EOF 22.211 +cat > "$PREFS/$USER2/confirmation_function" <<EOF 22.212 +add_to_quota_freebusy $QUOTA 22.213 +add_to_quota $QUOTA 22.214 +EOF 22.215 +cat > "$PREFS/$USER2/retraction_function" <<EOF 22.216 +remove_from_quota_freebusy $QUOTA 22.217 +remove_from_quota $QUOTA 22.218 +EOF 22.219 + 22.220 +# Remind the resource about the second event. 22.221 + 22.222 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-conflict.txt" 2>> $ERROR \ 22.223 +| tee out5r.tmp \ 22.224 +| "$SHOWMAIL" \ 22.225 +> out5.tmp 22.226 + 22.227 + grep -q 'METHOD:REPLY' out5.tmp \ 22.228 +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out5.tmp \ 22.229 +&& echo "Success" \ 22.230 +|| echo "Failed" 22.231 + 22.232 + [ -e "$FBFILE2" ] \ 22.233 +&& grep -q "^20141126T153000Z${TAB}20141126T163000Z" "$FBFILE2" \ 22.234 +&& echo "Success" \ 22.235 +|| echo "Failed" 22.236 + 22.237 +# Check the quota (event is still confirmed). 22.238 + 22.239 + [ -e "$JOURNALFILE" ] \ 22.240 +&& [ `grep "event21@example.com" "$JOURNALFILE" | wc -l` = '2' ] \ 22.241 +&& [ `grep "event22@example.com" "$JOURNALFILE" | wc -l` = '1' ] \ 22.242 +&& echo "Success" \ 22.243 +|| echo "Failed" 22.244 + 22.245 +# Attempt to schedule the first event. 22.246 + 22.247 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR 22.248 + 22.249 + grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ 22.250 +&& echo "Success" \ 22.251 +|| echo "Failed" 22.252 + 22.253 +# Present the request to the resource. 22.254 + 22.255 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car.txt" 2>> $ERROR \ 22.256 +| tee out6r.tmp \ 22.257 +| "$SHOWMAIL" \ 22.258 +> out6.tmp 22.259 + 22.260 + grep -q 'METHOD:REPLY' out6.tmp \ 22.261 +&& grep -q 'ATTENDEE.*;PARTSTAT=DECLINED' out6.tmp \ 22.262 +&& echo "Success" \ 22.263 +|| echo "Failed" 22.264 + 22.265 + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBFILE1" \ 22.266 +&& echo "Success" \ 22.267 +|| echo "Failed" 22.268 + 22.269 +# Check the quota (event is still retracted and not newly confirmed). 22.270 + 22.271 + [ -e "$JOURNALFILE" ] \ 22.272 +&& [ `grep "event21@example.com" "$JOURNALFILE" | wc -l` = '2' ] \ 22.273 +&& [ `grep "event22@example.com" "$JOURNALFILE" | wc -l` = '1' ] \ 22.274 +&& echo "Success" \ 22.275 +|| echo "Failed" 22.276 + 22.277 +# Attempt to schedule the first event moved earlier. 22.278 + 22.279 +"$OUTGOING_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-moved.txt" 2>> $ERROR 22.280 + 22.281 + ! grep -q "^20141126T150000Z${TAB}20141126T160000Z" "$FBSENDERFILE" \ 22.282 +&& grep -q "^20141126T143000Z${TAB}20141126T153000Z" "$FBSENDERFILE" \ 22.283 +&& echo "Success" \ 22.284 +|| echo "Failed" 22.285 + 22.286 +# Present the request to the resource. 22.287 + 22.288 + "$RESOURCE_SCRIPT" $ARGS < "$TEMPLATES/event-request-car-moved.txt" 2>> $ERROR \ 22.289 +| tee out7r.tmp \ 22.290 +| "$SHOWMAIL" \ 22.291 +> out7.tmp 22.292 + 22.293 + grep -q 'METHOD:REPLY' out7.tmp \ 22.294 +&& grep -q 'ATTENDEE.*;PARTSTAT=ACCEPTED' out7.tmp \ 22.295 +&& echo "Success" \ 22.296 +|| echo "Failed" 22.297 + 22.298 + grep -q "^20141126T143000Z${TAB}20141126T153000Z" "$FBFILE1" \ 22.299 +&& echo "Success" \ 22.300 +|| echo "Failed" 22.301 + 22.302 +# Check the quota (event is newly confirmed). 22.303 + 22.304 + [ -e "$JOURNALFILE" ] \ 22.305 +&& [ `grep "event21@example.com" "$JOURNALFILE" | wc -l` = '3' ] \ 22.306 +&& [ `grep "event22@example.com" "$JOURNALFILE" | wc -l` = '1' ] \ 22.307 +&& echo "Success" \ 22.308 +|| echo "Failed"
23.1 --- a/tools/fix.sh Sun Feb 07 23:35:20 2016 +0100 23.2 +++ b/tools/fix.sh Mon Feb 08 00:14:53 2016 +0100 23.3 @@ -32,7 +32,8 @@ 23.4 chown -R "$USER" "$INSTALL_DIR" 23.5 chgrp -R "$GROUP" "$INSTALL_DIR" 23.6 23.7 -for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do 23.8 +for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static \ 23.9 + "$INSTALL_DIR"/journal ; do 23.10 chown -R "$USER" "$DIR" 23.11 chgrp -R "$GROUP" "$DIR" 23.12 chmod -R g+w "$DIR"
24.1 --- a/tools/init.sh Sun Feb 07 23:35:20 2016 +0100 24.2 +++ b/tools/init.sh Mon Feb 08 00:14:53 2016 +0100 24.3 @@ -23,8 +23,9 @@ 24.4 Within the stored data directory (using $INSTALL_DIR as an example), the 24.5 following directories are created: 24.6 24.7 + * $INSTALL_DIR/journal 24.8 + * $INSTALL_DIR/preferences 24.9 * $INSTALL_DIR/store 24.10 - * $INSTALL_DIR/preferences 24.11 24.12 Within the published data directory (using $WEB_INSTALL_DIR as an example), the 24.13 following directory is created: 24.14 @@ -39,7 +40,8 @@ 24.15 USER=${3:-$IMIP_AGENT_USER} 24.16 GROUP=${4:-$IMIP_AGENT_GROUP} 24.17 24.18 -for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do 24.19 +for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static \ 24.20 + "$INSTALL_DIR"/journal ; do 24.21 mkdir -p "$DIR" 24.22 chown "$USER" "$DIR" 24.23 chgrp "$GROUP" "$DIR"
25.1 --- a/tools/init_user.sh Sun Feb 07 23:35:20 2016 +0100 25.2 +++ b/tools/init_user.sh Mon Feb 08 00:14:53 2016 +0100 25.3 @@ -29,7 +29,8 @@ 25.4 WEB_INSTALL_DIR=${3:-$WEB_INSTALL_DIR} 25.5 USER=${4:-$IMIP_AGENT_USER} 25.6 25.7 -for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static ; do 25.8 +for DIR in "$INSTALL_DIR"/store "$INSTALL_DIR"/preferences "$WEB_INSTALL_DIR"/static \ 25.9 + "$INSTALL_DIR"/journal ; do 25.10 mkdir -p "$DIR/$CALENDAR_USER" 25.11 chown "$USER" "$DIR/$CALENDAR_USER" 25.12 chmod g+ws "$DIR/$CALENDAR_USER"
26.1 --- a/tools/update_scheduling_modules.py Sun Feb 07 23:35:20 2016 +0100 26.2 +++ b/tools/update_scheduling_modules.py Mon Feb 08 00:14:53 2016 +0100 26.3 @@ -44,11 +44,26 @@ 26.4 26.5 f = open(join(dirname, "manifest.py"), "w") 26.6 try: 26.7 - print >>f, "scheduling_functions = {}" 26.8 + print >>f, """\ 26.9 +confirmation_functions = {} 26.10 +retraction_functions = {} 26.11 +scheduling_functions = {} 26.12 +""" 26.13 + 26.14 for filename in filenames: 26.15 module = splitext(filename)[0] 26.16 - print >>f, "from imiptools.handlers.scheduling.%s import scheduling_functions as l" % module 26.17 - print >>f, "scheduling_functions.update(l)" 26.18 + 26.19 + print >>f, """\ 26.20 +from imiptools.handlers.scheduling.%s import ( 26.21 + confirmation_functions as c, 26.22 + retraction_functions as r, 26.23 + scheduling_functions as s) 26.24 + 26.25 +confirmation_functions.update(c) 26.26 +retraction_functions.update(r) 26.27 +scheduling_functions.update(s) 26.28 +""" % module 26.29 + 26.30 finally: 26.31 f.close() 26.32