1##
2#    Copyright (c) 2007-2011 Cyrus Daboo. All rights reserved.
3#
4#    Licensed under the Apache License, Version 2.0 (the "License");
5#    you may not use this file except in compliance with the License.
6#    You may obtain a copy of the License at
7#
8#        http://www.apache.org/licenses/LICENSE-2.0
9#
10#    Unless required by applicable law or agreed to in writing, software
11#    distributed under the License is distributed on an "AS IS" BASIS,
12#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13#    See the License for the specific language governing permissions and
14#    limitations under the License.
15##
16
17from cStringIO import StringIO
18from pycalendar.datetimevalue import PyCalendarDateTimeValue
19from pycalendar.periodvalue import PyCalendarPeriodValue
20from pycalendar.property import PyCalendarProperty
21from pycalendar.value import PyCalendarValue
22
23class PyCalendarComponentBase(object):
24
25    def __init__(self, parent=None):
26        self.mParentComponent = parent
27        self.mComponents = []
28        self.mProperties = {}
29
30    def duplicate(self, **args):
31        other = self.__class__(**args)
32
33        other.mComponents = [component.duplicate(parent=other) for component in self.mComponents]
34
35        other.mProperties = {}
36        for propname, props in self.mProperties.iteritems():
37            other.mProperties[propname] = [i.duplicate() for i in props]
38        return other
39
40    def __str__(self):
41        return self.getText()
42
43    def __ne__(self, other): return not self.__eq__(other)
44    def __eq__(self, other):
45        if not isinstance(other, PyCalendarComponentBase): return False
46        return self.getType() == other.getType() and self.compareProperties(other) and self.compareComponents(other)
47
48    def getType(self):
49        raise NotImplementedError
50
51    def getBeginDelimiter(self):
52        return "BEGIN:" + self.getType()
53
54    def getEndDelimiter(self):
55        return "END:" + self.getType()
56
57    def getSortKey(self):
58        return ""
59
60    def getParentComponent(self):
61        return self.mParentComponent
62
63    def setParentComponent(self, parent):
64        self.mParentComponent = parent
65
66    def compareComponents(self, other):
67        mine = set(self.mComponents)
68        theirs = set(other.mComponents)
69
70        for item in mine:
71            for another in theirs:
72                if item == another:
73                    theirs.remove(another)
74                    break
75            else:
76                return False
77        return len(theirs) == 0
78
79    def getComponents(self, compname=None):
80        compname = compname.upper() if compname else None
81        return [component for component in self.mComponents if compname is None or component.getType().upper() == compname]
82
83    def getComponentByKey(self, key):
84        for component in self.mComponents:
85            if component.getMapKey() == key:
86                return component
87        else:
88            return None
89
90    def removeComponentByKey(self, key):
91
92        for component in self.mComponents:
93            if component.getMapKey() == key:
94                self.mComponents.remove(component)
95                return
96
97    def addComponent(self, component):
98        self.mComponents.append(component)
99
100    def hasComponent(self, compname):
101        return self.countComponents(compname) != 0
102
103    def countComponents(self, compname):
104        return len(self.getComponents(compname))
105
106    def removeComponent(self, component):
107        self.mComponents.remove(component)
108
109    def removeAllComponent(self, compname=None):
110        if compname:
111            compname = compname.upper()
112            for component in tuple(self.mComponents):
113                if component.getType().upper() == compname:
114                    self.mComponents.remove(component)
115        else:
116            self.mComponents = []
117
118    def sortedComponentNames(self):
119        return ()
120
121    def compareProperties(self, other):
122        mine = set()
123        for props in self.mProperties.values():
124            mine.update(props)
125        theirs = set()
126        for props in other.mProperties.values():
127            theirs.update(props)
128        return mine == theirs
129
130    def getProperties(self, propname=None):
131        return self.mProperties.get(propname.upper(), []) if propname else self.mProperties
132
133    def setProperties(self, props):
134        self.mProperties = props
135
136    def addProperty(self, prop):
137        self.mProperties.setdefault(prop.getName().upper(), []).append(prop)
138
139    def hasProperty(self, propname):
140        return self.mProperties.has_key(propname.upper())
141
142    def countProperty(self, propname):
143        return len(self.mProperties.get(propname.upper(), []))
144
145    def findFirstProperty(self, propname):
146        return self.mProperties.get(propname.upper(), [None])[0]
147
148    def removeProperty(self, prop):
149        if self.mProperties.has_key(prop.getName().upper()):
150            self.mProperties[prop.getName().upper()].remove(prop)
151            if len(self.mProperties[prop.getName().upper()]) == 0:
152                del self.mProperties[prop.getName().upper()]
153
154    def removeProperties(self, propname):
155        if self.mProperties.has_key(propname.upper()):
156            del self.mProperties[propname.upper()]
157
158    def getPropertyInteger(self, prop, type = None):
159        return self.loadValueInteger(prop, type)
160
161    def getPropertyString(self, prop):
162        return self.loadValueString(prop)
163
164    def getProperty(self, prop, value):
165        return self.loadValue(prop, value)
166
167    def finalise(self):
168        raise NotImplemented
169
170    def getText(self):
171        s = StringIO()
172        self.generate(s)
173        return s.getvalue()
174
175    def generate(self, os):
176        # Header
177        os.write(self.getBeginDelimiter())
178        os.write("\r\n")
179
180        # Write each property
181        self.writeProperties(os)
182
183        # Write each embedded component based on specific order
184        self.writeComponents(os)
185
186        # Footer
187        os.write(self.getEndDelimiter())
188        os.write("\r\n")
189
190    def generateFiltered(self, os, filter):
191        # Header
192        os.write(self.getBeginDelimiter())
193        os.write("\r\n")
194
195        # Write each property
196        self.writePropertiesFiltered(os, filter)
197
198        # Write each property
199        self.writeComponentsFiltered(os, filter)
200
201        # Footer
202        os.write(self.getEndDelimiter())
203        os.write("\r\n")
204
205    def sortedComponents(self):
206
207        components = self.mComponents[:]
208        sortedcomponents = []
209
210        # Write each component based on specific order
211        orderedNames = self.sortedComponentNames()
212        for name in orderedNames:
213
214            # Group by name then sort by map key (UID/R-ID)
215            namedcomponents = []
216            for component in tuple(components):
217                if component.getType().upper() == name:
218                    namedcomponents.append(component)
219                    components.remove(component)
220            for component in sorted(namedcomponents, key=lambda x:x.getSortKey()):
221                sortedcomponents.append(component)
222
223        # Write out the remainder
224        for component in components:
225            sortedcomponents.append(component)
226
227        return sortedcomponents
228
229    def writeComponents(self, os):
230
231        # Write out the remainder
232        for component in self.sortedComponents():
233            component.generate(os)
234
235    def writeComponentsFiltered(self, os, filter):
236        # Shortcut for all sub-components
237        if filter.isAllSubComponents():
238            self.writeComponents(os)
239        elif filter.hasSubComponentFilters():
240            for subcomp in self.sortedcomponents():
241                subfilter = filter.getSubComponentFilter(subcomp.getType())
242                if subfilter is not None:
243                    subcomp.generateFiltered(os, subfilter)
244
245    def loadValue(self, value_name):
246        if self.hasProperty(value_name):
247            return self.findFirstProperty(value_name)
248
249        return None
250
251    def loadValueInteger(self, value_name, type=None):
252        if type:
253            if self.hasProperty(value_name):
254                if type == PyCalendarValue.VALUETYPE_INTEGER:
255                    ivalue = self.findFirstProperty(value_name).getIntegerValue()
256                    if ivalue is not None:
257                        return ivalue.getValue()
258                elif type == PyCalendarValue.VALUETYPE_UTC_OFFSET:
259                    uvalue = self.findFirstProperty(value_name).getUTCOffsetValue()
260                    if (uvalue is not None):
261                        return uvalue.getValue()
262
263            return None
264        else:
265            return self.loadValueInteger(value_name, PyCalendarValue.VALUETYPE_INTEGER)
266
267    def loadValueString(self, value_name):
268        if self.hasProperty(value_name):
269            tvalue = self.findFirstProperty(value_name).getTextValue()
270            if (tvalue is not None):
271                return tvalue.getValue()
272
273        return None
274
275    def loadValueDateTime(self, value_name):
276        if self.hasProperty(value_name):
277            dtvalue = self.findFirstProperty(value_name).getDateTimeValue()
278            if dtvalue is not None:
279                return dtvalue.getValue()
280
281        return None
282
283    def loadValueDuration(self, value_name):
284        if self.hasProperty(value_name):
285            dvalue = self.findFirstProperty(value_name).getDurationValue()
286            if (dvalue is not None):
287                return dvalue.getValue()
288
289        return None
290
291    def loadValuePeriod(self, value_name):
292        if self.hasProperty(value_name):
293            pvalue = self.findFirstProperty(value_name).getPeriodValue()
294            if (pvalue is not None):
295                return pvalue.getValue()
296
297        return None
298
299    def loadValueRRULE(self, value_name, value, add):
300        # Get RRULEs
301        if self.hasProperty(value_name):
302            items = self.getProperties()[value_name]
303            for iter in items:
304                rvalue = iter.getRecurrenceValue()
305                if (rvalue is not None):
306                    if add:
307                        value.addRule(rvalue.getValue())
308                    else:
309                        value.subtractRule(rvalue.getValue())
310            return True
311        else:
312            return False
313
314    def loadValueRDATE(self, value_name, value, add):
315        # Get RDATEs
316        if self.hasProperty(value_name):
317            for iter in self.getProperties(value_name):
318                mvalue = iter.getMultiValue()
319                if (mvalue is not None):
320                    for obj in mvalue.getValues():
321                        # cast to date-time
322                        if isinstance(obj, PyCalendarDateTimeValue):
323                            if add:
324                                value.addDT(obj.getValue())
325                            else:
326                                value.subtractDT(obj.getValue())
327                        elif isinstance(obj, PyCalendarPeriodValue):
328                            if add:
329                                value.addPeriod(obj.getValue().getStart())
330                            else:
331                                value.subtractPeriod(obj.getValue().getStart())
332
333            return True
334        else:
335            return False
336
337    def sortedPropertyKeys(self):
338        keys = self.mProperties.keys()
339        keys.sort()
340
341        results = []
342        for skey in self.sortedPropertyKeyOrder():
343            if skey in keys:
344                results.append(skey)
345                keys.remove(skey)
346        results.extend(keys)
347        return results
348
349    def sortedPropertyKeyOrder(self):
350        return ()
351
352    def writeProperties(self, os):
353        # Sort properties by name
354        keys = self.sortedPropertyKeys()
355        for key in keys:
356            props = self.mProperties[key]
357            for prop in props:
358                prop.generate(os)
359
360    def writePropertiesFiltered(self, os, filter):
361
362        # Sort properties by name
363        keys = self.sortedPropertyKeys()
364
365        # Shortcut for all properties
366        if filter.isAllProperties():
367            for key in keys:
368                for prop in self.getProperties(key):
369                    prop.generate(os)
370        elif filter.hasPropertyFilters():
371            for key in keys:
372                for prop in self.getProperties(key):
373                    prop.generateFiltered(os, filter)
374
375    def loadPrivateValue(self, value_name):
376        # Read it in from properties list and then delete the property from the
377        # main list
378        result = self.loadValueString(value_name)
379        if (result is not None):
380            self.removeProperties(value_name)
381        return result
382
383    def writePrivateProperty(self, os, key, value):
384        prop = PyCalendarProperty(name=key, value=value)
385        prop.generate(os)
386
387    def editProperty(self, propname, propvalue):
388
389        # Remove existing items
390        self.removeProperties(propname)
391
392        # Now create properties
393        if propvalue:
394            self.addProperty(PyCalendarProperty(name=propname, value=propvalue))
395