forked from sanyaade-machine-learning/Transana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Library.py
377 lines (346 loc) · 15 KB
/
Library.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# Copyright (C) 2003-2015 The Board of Regents of the University of Wisconsin System
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
"""This module implements the Library class (formerly the Series class) as part of the Data Objects."""
__author__ = 'David Woods <[email protected]>, Nathaniel Case'
DEBUG = False
if DEBUG:
print "Library DEBUG is ON!"
# Import wxPython
import wx
# import Python's types module
import types
# Import Transana's base Data Object
import DataObject
# import Transana's Database Interface
import DBInterface
# import Transana Dialogs
import Dialogs
# import Transana Document object
import Document
# import Transana's Episode object
import Episode
# Import Transana's Note object
import Note
# import Transana's Constants
import TransanaConstants
# import Transana's Exceptions
from TransanaExceptions import *
# import Transana's Globals
import TransanaGlobal
class Library(DataObject.DataObject):
"""This class defines the structure for a Library object. A Library object
holds information about a group (e.g., a Library) of data files
(e.g., Documents and Episodes)."""
def __init__(self, id_or_num=None):
"""Initialize an Library object. If a record ID number or Library ID
is given, load it from the database."""
DataObject.DataObject.__init__(self)
if type(id_or_num) in (int, long):
self.db_load_by_num(id_or_num)
elif isinstance(id_or_num, types.StringTypes):
self.db_load_by_name(id_or_num)
def __repr__(self):
""" String Representation of a Library Object """
str = 'Library Object:\n'
str += 'number = %s\n' % self.number
str += 'id = %s (%s)\n' % (self.id.encode('utf8'), type(self.id))
str += 'comment = %s\n' % self.comment.encode('utf8')
str += 'owner = %s\n' % self.owner.encode('utf8')
str += 'keyword_group = %s\n\n' % self.keyword_group.encode('utf8')
# str += "isLocked = %s\n" % self._isLocked
# str += "recordlock = %s\n" % self.recordlock
# str += "locktime = %s\n" % self.locktime
return str
def __eq__(self, other):
""" Object equality check """
if other == None:
return False
else:
return self.__dict__ == other.__dict__
# Public methods
def db_load_by_name(self, name):
"""Load a record by ID / Name. Raise a RecordNotFound exception
if record is not found in database."""
# If we're in Unicode mode, we need to encode the parameter so that the query will work right.
if 'unicode' in wx.PlatformInfo:
name = name.encode(TransanaGlobal.encoding)
# Get the database connection
db = DBInterface.get_db()
# Define the load query
query = """
SELECT * FROM Series2
WHERE SeriesID = %s
"""
# Adjust the query for sqlite if needed
query = DBInterface.FixQuery(query)
# Get a database cursor
c = db.cursor()
# Execute the query
c.execute(query, (name, ))
# rowcount doesn't work for sqlite!
if TransanaConstants.DBInstalled == 'sqlite3':
# ... so assume one row return for now
n = 1
# If not sqlite ...
else:
# ... we can use rowcount
n = c.rowcount
# If not one row ...
if (n != 1):
# ... close the database cursor ...
c.close()
# ... clear the current series ...
self.clear()
# ... and raise an exception
raise RecordNotFoundError, (name, n)
# If exactly one row, or sqlite ...
else:
# ... get the query results ...
r = DBInterface.fetch_named(c)
# If sqlite and no results ...
if (TransanaConstants.DBInstalled == 'sqlite3') and (r == {}):
# ... close the database cursor ...
c.close()
# ... clear the current series ...
self.clear()
# ... and raise an exception
raise RecordNotFoundError, (name, 0)
# Load the database results into the Series object
self._load_row(r)
# Close the database cursor ...
c.close()
def db_load_by_num(self, num):
"""Load a record by record number."""
# Get the database connection
db = DBInterface.get_db()
# Define the load query
query = """
SELECT * FROM Series2
WHERE SeriesNum = %s
"""
# Adjust the query for sqlite if needed
query = DBInterface.FixQuery(query)
# Get a database cursor
c = db.cursor()
# Execute the query
c.execute(query, (num, ))
# rowcount doesn't work for sqlite!
if TransanaConstants.DBInstalled == 'sqlite3':
# ... so assume one result for now
n = 1
# If not sqlite ...
else:
# ... we can use rowcount
n = c.rowcount
# If something other than one record is returned ...
if (n != 1):
# ... close the database cursor ...
c.close()
# ... clear the current Library object ...
self.clear()
# ... and raise an exception
raise RecordNotFoundError, (num, n)
# If exactly one row is returned, or we're using sqlite ...
else:
# ... load the query results ...
r = DBInterface.fetch_named(c)
# If sqlite and no data is loaded ...
if (TransanaConstants.DBInstalled == 'sqlite3') and (r == {}):
# ... close the database cursor ...
c.close()
# ... clear the current Library object ...
self.clear()
# ... and raise an exception
raise RecordNotFoundError, (num, 0)
# Place the loaded data in the object
self._load_row(r)
# Close the database cursor ...
c.close()
def db_save(self, use_transactions=True):
"""Save the record to the database using Insert or Update as
appropriate."""
# Sanity checks
if self.id == "":
raise SaveError, _("Library ID is required.")
# If we're in Unicode mode, ...
if 'unicode' in wx.PlatformInfo:
# Encode strings to UTF8 before saving them. The easiest way to handle this is to create local
# variables for the data. We don't want to change the underlying object values. Also, this way,
# we can continue to use the Unicode objects where we need the non-encoded version. (error messages.)
id = self.id.encode(TransanaGlobal.encoding)
comment = self.comment.encode(TransanaGlobal.encoding)
owner = self.owner.encode(TransanaGlobal.encoding)
keyword_group = self.keyword_group.encode(TransanaGlobal.encoding)
else:
# If we don't need to encode the string values, we still need to copy them to our local variables.
id = self.id
comment = self.comment
owner = self.owner
keyword_group = self.keyword_group
values = (id, comment, owner, keyword_group)
if (self._db_start_save() == 0):
# duplicate Library IDs are not allowed
if DBInterface.record_match_count("Series2", \
("SeriesID",), \
(id,) ) > 0:
if 'unicode' in wx.PlatformInfo:
# Encode with UTF-8 rather than TransanaGlobal.encoding because this is a prompt, not DB Data.
prompt = unicode(_('A Library named "%s" already exists.\nPlease enter a different Library ID.'), 'utf8')
else:
prompt = _('A Library named "%s" already exists.\nPlease enter a different Library ID.')
raise SaveError, prompt % self.id
# insert the new Library
query = """
INSERT INTO Series2
(SeriesID, SeriesComment, SeriesOwner, DefaultKeywordGroup)
VALUES
(%s, %s, %s, %s)
"""
else:
# check for dupes
if DBInterface.record_match_count("Series2", \
("SeriesID", "!SeriesNum"), \
(id, self.number) ) > 0:
if 'unicode' in wx.PlatformInfo:
# Encode with UTF-8 rather than TransanaGlobal.encoding because this is a prompt, not DB Data.
prompt = unicode(_('A Library named "%s" already exists.\nPlease enter a different Library ID.'), 'utf8')
else:
prompt = _('A Library named "%s" already exists.\nPlease enter a different Library ID.')
raise SaveError, prompt % self.id
# update the record
query = """
UPDATE Series2
SET SeriesID = %s,
SeriesComment = %s,
SeriesOwner = %s,
DefaultKeywordGroup = %s
WHERE SeriesNum = %s
"""
values = values + (self.number,)
# Get a database cursor
c = DBInterface.get_db().cursor()
# Adjust the query for sqlite if needed
query = DBInterface.FixQuery(query)
# Execute the query
c.execute(query, values)
# Close the database cursor
c.close()
# if we saved a new Library, NUM was auto-assigned so our
# 'local' data is out of date. re-sync
if (self.number == 0):
self.db_load_by_name(self.id)
def db_delete(self, use_transactions=1):
"""Delete this object record from the database. Raises
RecordLockedError exception if record is locked and unable to be
deleted."""
# Assume success
result = 1
try:
# Initialize delete operation, begin transaction if necessary.
(db, c) = self._db_start_delete(use_transactions)
if (db == None):
return # Abort delete without even trying
# Delete all Library-based Filter Configurations
# Delete Library Keyword Sequence Map records
DBInterface.delete_filter_records(5, self.number)
# Delete Library Keyword Bar Graph records
DBInterface.delete_filter_records(6, self.number)
# Delete Library Keyword Percentage Map records
DBInterface.delete_filter_records(7, self.number)
# Delete Library Report records
DBInterface.delete_filter_records(10, self.number)
# Delete Library Clip Data Export records
DBInterface.delete_filter_records(14, self.number)
# Detect, Load, and Delete all Library Notes.
notes = self.get_note_nums()
for note_num in notes:
note = Note.Note(note_num)
result = result and note.db_delete(0)
del note
del notes
# Deletes Episodes, which in turn will delete Episode Transcripts,
# Episode Notes, and Episode Keywords
episodes = DBInterface.list_of_episodes_for_series(self.id)
for (episode_num, episode_id, series_num) in episodes:
episode = Episode.Episode(episode_num)
# Store the result so we can rollback the transaction on failure
result = result and episode.db_delete(0)
del episode
del episodes
# Deletes Documents, which in turn will delete Document Notes and Document Keywords
documents = DBInterface.list_of_documents(self.number)
for (document_num, document_id, library_num) in documents:
document = Document.Document(document_num)
# Store the result so we can rollback the transaction on failure
result = result and document.db_delete(0)
del document
del documents
# Delete the actual record.
self._db_do_delete(use_transactions, c, result)
# Cleanup
c.close()
self.clear()
except RecordLockedError, e:
# if a sub-record is locked, we may need to unlock the Library record (after rolling back the Transaction)
if self.isLocked:
# c (the database cursor) only exists if the record lock was obtained!
# We must roll back the transaction before we unlock the record.
c.execute("ROLLBACK")
c.close()
self.unlock_record()
raise e
# Handle the DeleteError Exception
except DeleteError, e:
# If the record is locked ...
if self.isLocked:
# ... then unlock it ...
self.unlock_record()
# ... and pass on the exception.
raise e
except:
raise
return result
# Private methods
def _load_row(self, r):
self.number = r['SeriesNum']
self.id = r['SeriesID']
self.comment = r['SeriesComment']
self.owner = r['SeriesOwner']
self.keyword_group = r['DefaultKeywordGroup']
# If we're in Unicode mode, we need to encode the data from the database appropriately.
# (unicode(var, TransanaGlobal.encoding) doesn't work, as the strings are already unicode, yet aren't decoded.)
if 'unicode' in wx.PlatformInfo:
self.id = DBInterface.ProcessDBDataForUTF8Encoding(self.id)
self.comment = DBInterface.ProcessDBDataForUTF8Encoding(self.comment)
self.owner = DBInterface.ProcessDBDataForUTF8Encoding(self.owner)
self.keyword_group = DBInterface.ProcessDBDataForUTF8Encoding(self.keyword_group)
def _get_owner(self):
return self._owner
def _set_owner(self, owner):
self._owner = owner
def _del_owner(self):
self._owner = ""
def _get_kg(self):
return self._kg
def _set_kg(self, kg):
self._kg = kg
def _del_kg(self):
self._kg = ""
# Public properties
owner = property(_get_owner, _set_owner, _del_owner,
"""The person responsible for creating or maintaining the Library.""")
keyword_group = property(_get_kg, _set_kg, _del_kg,
"""The default keyword group to be suggested for all new Episodes.""")