1.1 --- a/imiptools/freebusy.py Wed May 24 15:41:14 2017 +0200
1.2 +++ b/imiptools/freebusy.py Fri May 26 16:52:25 2017 +0200
1.3 @@ -24,18 +24,62 @@
1.4 from imiptools.period import get_overlapping, Period, PeriodBase
1.5 from imiptools.sql import DatabaseOperations
1.6
1.7 +try:
1.8 + from cStringIO import StringIO
1.9 +except ImportError:
1.10 + from StringIO import StringIO
1.11 +
1.12 def from_string(s, encoding):
1.13 +
1.14 + "Interpret 's' using 'encoding', preserving None."
1.15 +
1.16 if s:
1.17 return unicode(s, encoding)
1.18 else:
1.19 return s
1.20
1.21 def to_string(s, encoding):
1.22 +
1.23 + "Encode 's' using 'encoding', preserving None."
1.24 +
1.25 if s:
1.26 return s.encode(encoding)
1.27 else:
1.28 return s
1.29
1.30 +def to_copy_string(s, encoding):
1.31 +
1.32 + """
1.33 + Encode 's' using 'encoding' as a string suitable for use in tabular data
1.34 + acceptable to the PostgreSQL COPY command with \N as null.
1.35 + """
1.36 +
1.37 + s = to_string(s, encoding)
1.38 + return s is None and "\\N" or s
1.39 +
1.40 +def to_copy_file(records):
1.41 +
1.42 + """
1.43 + Encode the given 'records' and store them in a file-like object for use with
1.44 + a tabular import mechanism. Return the file-like object.
1.45 + """
1.46 +
1.47 + io = StringIO()
1.48 + for values in records:
1.49 + l = []
1.50 + for v in values:
1.51 + l.append(to_copy_string(v, "utf-8"))
1.52 + io.write("\t".join(l))
1.53 + io.write("\n")
1.54 + io.seek(0)
1.55 + return io
1.56 +
1.57 +def quote_column(column):
1.58 +
1.59 + "Quote 'column' using the SQL keyword quoting notation."
1.60 +
1.61 + return '"%s"' % column
1.62 +
1.63 class FreeBusyPeriod(PeriodBase):
1.64
1.65 "A free/busy record abstraction."
1.66 @@ -265,8 +309,7 @@
1.67 # List emulation methods.
1.68
1.69 def __iadd__(self, periods):
1.70 - for period in periods:
1.71 - self.insert_period(period)
1.72 + self.insert_periods(periods)
1.73 return self
1.74
1.75 def append(self, period):
1.76 @@ -274,6 +317,13 @@
1.77
1.78 # Operations.
1.79
1.80 + def insert_periods(self, periods):
1.81 +
1.82 + "Insert the given 'periods' into the collection."
1.83 +
1.84 + for p in periods:
1.85 + self.insert_period(p)
1.86 +
1.87 def can_schedule(self, periods, uid, recurrenceid):
1.88
1.89 """
1.90 @@ -409,8 +459,7 @@
1.91
1.92 self.remove_specific_event_periods(uid, recurrenceid)
1.93
1.94 - for p in periods:
1.95 - self.insert_period(p)
1.96 + self.insert_periods(periods)
1.97
1.98 def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser):
1.99
1.100 @@ -446,8 +495,7 @@
1.101
1.102 self.remove_specific_event_periods(uid, recurrenceid, attendee)
1.103
1.104 - for p in periods:
1.105 - self.insert_period(p)
1.106 + self.insert_periods(periods)
1.107
1.108 def update_freebusy(self, periods, transp, uid, recurrenceid, summary, organiser, attendee=None):
1.109
1.110 @@ -790,6 +838,27 @@
1.111
1.112 self.cursor.execute(query, values)
1.113
1.114 + def insert_periods(self, periods):
1.115 +
1.116 + "Insert the given 'periods' into the collection."
1.117 +
1.118 + if not hasattr(self.cursor, "copy_from"):
1.119 + return FreeBusyCollectionBase.insert_periods(self, periods)
1.120 +
1.121 + self._check_mutable()
1.122 +
1.123 + columns = self.merge_default_columns(self.period_columns)
1.124 +
1.125 + all_values = []
1.126 + for period in periods:
1.127 + all_values.append(self.merge_default_values(period.as_tuple(string_datetimes=True)))
1.128 +
1.129 + f = to_copy_file(all_values)
1.130 +
1.131 + # Copy from the file-like object to the table.
1.132 +
1.133 + self.cursor.copy_from(f, self.table_name, columns=map(quote_column, columns))
1.134 +
1.135 def remove_periods(self, periods):
1.136
1.137 "Remove the given 'periods' from the collection."