imip-agent

imip_store.py

336:9807df0a5e95
2015-02-12 Paul Boddie Merged changes from the default branch. recurring-events
     1 #!/usr/bin/env python     2      3 """     4 A simple filesystem-based store of calendar data.     5      6 Copyright (C) 2014, 2015 Paul Boddie <paul@boddie.org.uk>     7      8 This program is free software; you can redistribute it and/or modify it under     9 the terms of the GNU General Public License as published by the Free Software    10 Foundation; either version 3 of the License, or (at your option) any later    11 version.    12     13 This program is distributed in the hope that it will be useful, but WITHOUT    14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS    15 FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more    16 details.    17     18 You should have received a copy of the GNU General Public License along with    19 this program.  If not, see <http://www.gnu.org/licenses/>.    20 """    21     22 from datetime import datetime    23 from imiptools.config import STORE_DIR, PUBLISH_DIR    24 from imiptools.data import make_calendar, parse_object, to_stream    25 from imiptools.filesys import fix_permissions, FileBase    26 from os.path import exists, isfile, join    27 from os import listdir, remove    28 from time import sleep    29     30 class FileStore(FileBase):    31     32     "A file store of tabular free/busy data and objects."    33     34     def __init__(self, store_dir=STORE_DIR):    35         FileBase.__init__(self, store_dir)    36     37     def acquire_lock(self, user, timeout=None):    38         FileBase.acquire_lock(self, timeout, user)    39     40     def release_lock(self, user):    41         FileBase.release_lock(self, user)    42     43     def _get_object(self, user, filename):    44     45         """    46         Return the parsed object for the given 'user' having the given    47         'filename'.    48         """    49     50         self.acquire_lock(user)    51         try:    52             f = open(filename, "rb")    53             try:    54                 return parse_object(f, "utf-8")    55             finally:    56                 f.close()    57         finally:    58             self.release_lock(user)    59     60     def _set_object(self, user, filename, node):    61     62         """    63         Set an object for the given 'user' having the given 'filename', using    64         'node' to define the object.    65         """    66     67         self.acquire_lock(user)    68         try:    69             f = open(filename, "wb")    70             try:    71                 to_stream(f, node)    72             finally:    73                 f.close()    74                 fix_permissions(filename)    75         finally:    76             self.release_lock(user)    77     78         return True    79     80     def _remove_object(self, filename):    81     82         "Remove the object with the given 'filename'."    83     84         try:    85             remove(filename)    86         except OSError:    87             return False    88     89         return True    90     91     def get_events(self, user):    92     93         "Return a list of event identifiers."    94     95         filename = self.get_object_in_store(user, "objects")    96         if not filename or not exists(filename):    97             return None    98     99         return [name for name in listdir(filename) if isfile(join(filename, name))]   100    101     def get_event(self, user, uid):   102    103         "Get the event for the given 'user' with the given 'uid'."   104    105         filename = self.get_object_in_store(user, "objects", uid)   106         if not filename or not exists(filename):   107             return None   108    109         return self._get_object(user, filename)   110    111     def set_event(self, user, uid, node):   112    113         "Set an event for 'user' having the given 'uid' and 'node'."   114    115         filename = self.get_object_in_store(user, "objects", uid)   116         if not filename:   117             return False   118    119         return self._set_object(user, filename, node)   120    121     def remove_event(self, user, uid):   122    123         "Remove an event for 'user' having the given 'uid'."   124    125         filename = self.get_object_in_store(user, "objects", uid)   126         if not filename:   127             return False   128    129         return self._remove_object(filename)   130    131     def get_recurrences(self, user, uid):   132    133         """   134         Get additional event instances for an event of the given 'user' with the   135         indicated 'uid'.   136         """   137    138         filename = self.get_object_in_store(user, "recurrences", uid)   139         if not filename or not exists(filename):   140             return None   141    142         return [name for name in listdir(filename) if isfile(join(filename, name))]   143    144     def get_recurrence(self, user, uid, recurrenceid):   145    146         """   147         For the event of the given 'user' with the given 'uid', return the   148         specific recurrence indicated by the 'recurrenceid'.   149         """   150    151         filename = self.get_object_in_store(user, "recurrences", uid, recurrenceid)   152         if not filename or not exists(filename):   153             return None   154    155         return self._get_object(user, filename)   156    157     def set_recurrence(self, user, uid, recurrenceid, node):   158    159         "Set an event for 'user' having the given 'uid' and 'node'."   160    161         filename = self.get_object_in_store(user, "recurrences", uid, recurrenceid)   162         if not filename:   163             return False   164    165         return self._set_object(user, filename, node)   166    167     def remove_recurrence(self, user, uid, recurrenceid):   168    169         "Remove an event for 'user' having the given 'uid'."   170    171         filename = self.get_object_in_store(user, "objects", uid)   172         if not filename:   173             return False   174    175         return self._remove_object(filename)   176    177     def get_freebusy(self, user):   178    179         "Get free/busy details for the given 'user'."   180    181         filename = self.get_object_in_store(user, "freebusy")   182         if not filename or not exists(filename):   183             return []   184         else:   185             return self._get_freebusy(user, filename)   186    187     def get_freebusy_for_other(self, user, other):   188    189         "For the given 'user', get free/busy details for the 'other' user."   190    191         filename = self.get_object_in_store(user, "freebusy-other", other)   192         if not filename or not exists(filename):   193             return []   194         else:   195             return self._get_freebusy(user, filename)   196    197     def _get_freebusy(self, user, filename):   198    199         "For the given 'user', get the free/busy details from 'filename'."   200    201         self.acquire_lock(user)   202         try:   203             f = open(filename)   204             try:   205                 l = []   206                 for line in f.readlines():   207                     l.append(tuple(line.strip().split("\t")))   208                 return l   209             finally:   210                 f.close()   211         finally:   212             self.release_lock(user)   213    214     def set_freebusy(self, user, freebusy):   215    216         "For the given 'user', set 'freebusy' details."   217    218         filename = self.get_object_in_store(user, "freebusy")   219         if not filename:   220             return False   221    222         self._set_freebusy(user, filename, freebusy)   223         return True   224    225     def set_freebusy_for_other(self, user, freebusy, other):   226    227         "For the given 'user', set 'freebusy' details for the 'other' user."   228    229         filename = self.get_object_in_store(user, "freebusy-other", other)   230         if not filename:   231             return False   232    233         self._set_freebusy(user, filename, freebusy)   234         return True   235    236     def _set_freebusy(self, user, filename, freebusy):   237    238         """   239         For the given 'user', write to the file having the given 'filename' the   240         'freebusy' details.   241         """   242    243         self.acquire_lock(user)   244         try:   245             f = open(filename, "w")   246             try:   247                 for item in freebusy:   248                     f.write("\t".join([(value or "OPAQUE") for value in item]) + "\n")   249             finally:   250                 f.close()   251                 fix_permissions(filename)   252         finally:   253             self.release_lock(user)   254    255     def _get_requests(self, user, queue):   256    257         "Get requests for the given 'user' from the given 'queue'."   258    259         filename = self.get_object_in_store(user, queue)   260         if not filename or not exists(filename):   261             return None   262    263         self.acquire_lock(user)   264         try:   265             f = open(filename)   266             try:   267                 return [line.strip() for line in f.readlines()]   268             finally:   269                 f.close()   270         finally:   271             self.release_lock(user)   272    273     def get_requests(self, user):   274    275         "Get requests for the given 'user'."   276    277         return self._get_requests(user, "requests")   278    279     def get_cancellations(self, user):   280    281         "Get cancellations for the given 'user'."   282    283         return self._get_requests(user, "cancellations")   284    285     def _set_requests(self, user, requests, queue):   286    287         """   288         For the given 'user', set the list of queued 'requests' in the given   289         'queue'.   290         """   291    292         filename = self.get_object_in_store(user, queue)   293         if not filename:   294             return False   295    296         self.acquire_lock(user)   297         try:   298             f = open(filename, "w")   299             try:   300                 for request in requests:   301                     print >>f, request   302             finally:   303                 f.close()   304                 fix_permissions(filename)   305         finally:   306             self.release_lock(user)   307    308         return True   309    310     def set_requests(self, user, requests):   311    312         "For the given 'user', set the list of queued 'requests'."   313    314         return self._set_requests(user, requests, "requests")   315    316     def set_cancellations(self, user, cancellations):   317    318         "For the given 'user', set the list of queued 'cancellations'."   319    320         return self._set_requests(user, cancellations, "cancellations")   321    322     def _set_request(self, user, request, queue):   323    324         "For the given 'user', set the queued 'request' in the given 'queue'."   325    326         filename = self.get_object_in_store(user, queue)   327         if not filename:   328             return False   329    330         self.acquire_lock(user)   331         try:   332             f = open(filename, "a")   333             try:   334                 print >>f, request   335             finally:   336                 f.close()   337                 fix_permissions(filename)   338         finally:   339             self.release_lock(user)   340    341         return True   342    343     def set_request(self, user, request):   344    345         "For the given 'user', set the queued 'request'."   346    347         return self._set_request(user, request, "requests")   348    349     def set_cancellation(self, user, cancellation):   350    351         "For the given 'user', set the queued 'cancellation'."   352    353         return self._set_request(user, cancellation, "cancellations")   354    355     def queue_request(self, user, uid):   356    357         "Queue a request for 'user' having the given 'uid'."   358    359         requests = self.get_requests(user) or []   360    361         if uid not in requests:   362             return self.set_request(user, uid)   363    364         return False   365    366     def dequeue_request(self, user, uid):   367    368         "Dequeue a request for 'user' having the given 'uid'."   369    370         requests = self.get_requests(user) or []   371    372         try:   373             requests.remove(uid)   374             self.set_requests(user, requests)   375         except ValueError:   376             return False   377         else:   378             return True   379    380     def cancel_event(self, user, uid):   381    382         "Queue an event for cancellation for 'user' having the given 'uid'."   383    384         cancellations = self.get_cancellations(user) or []   385    386         if uid not in cancellations:   387             return self.set_cancellation(user, uid)   388    389         return False   390    391 class FilePublisher(FileBase):   392    393     "A publisher of objects."   394    395     def __init__(self, store_dir=PUBLISH_DIR):   396         FileBase.__init__(self, store_dir)   397    398     def set_freebusy(self, user, freebusy):   399    400         "For the given 'user', set 'freebusy' details."   401    402         filename = self.get_object_in_store(user, "freebusy")   403         if not filename:   404             return False   405    406         record = []   407         rwrite = record.append   408    409         rwrite(("ORGANIZER", {}, user))   410         rwrite(("UID", {}, user))   411         rwrite(("DTSTAMP", {}, datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")))   412    413         for start, end, uid, transp in freebusy:   414             if not transp or transp == "OPAQUE":   415                 rwrite(("FREEBUSY", {"FBTYPE" : "BUSY"}, "/".join([start, end])))   416    417         f = open(filename, "w")   418         try:   419             to_stream(f, make_calendar([("VFREEBUSY", {}, record)], "PUBLISH"))   420         finally:   421             f.close()   422             fix_permissions(filename)   423    424         return True   425    426 # vim: tabstop=4 expandtab shiftwidth=4