/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/Model/session.py
ViewVC logotype

Annotation of /branches/WIP-pyshapelib-bramz/Thuban/Model/session.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2734 - (hide annotations)
Thu Mar 1 12:42:59 2007 UTC (18 years ago) by bramz
File MIME type: text/x-python
File size: 15639 byte(s)
made a copy
1 bh 2102 # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4 jan 197 # Jan-Oliver Wagner <[email protected]>
5 bh 6 #
6     # This program is free software under the GPL (>=v2)
7     # Read the file COPYING coming with Thuban for details.
8    
9     __version__ = "$Revision$"
10    
11 bh 765 import os
12     from tempfile import mktemp
13     import weakref
14    
15 jan 197 from messages import MAPS_CHANGED, EXTENSIONS_CHANGED, FILENAME_CHANGED, \
16 jonathan 548 MAP_LAYERS_CHANGED, MAP_PROJECTION_CHANGED, \
17 jonathan 582 LAYER_CHANGED, LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED,\
18 bh 1068 EXTENSION_CHANGED, EXTENSION_OBJECTS_CHANGED, CHANGED, \
19 bh 1607 TABLE_REMOVED, DBCONN_ADDED, DBCONN_REMOVED
20 bh 6
21 jan 374 from Thuban import _
22    
23 bh 6 from base import TitledObject, Modifiable
24     from map import Map
25 bh 723 from data import ShapefileStore
26 bh 1036 from table import DBFTable
27 bh 1607 import postgisdb
28 bh 6
29 bh 982 from transientdb import TransientDatabase, AutoTransientTable
30 bh 6
31 bh 765 class AutoRemoveFile:
32    
33     """Remove a file once all references go away."""
34    
35     def __init__(self, filename, tempdir = None):
36     """Initialize the AutoRemoveFile
37    
38     Parameters:
39     filename -- The name of the file to remove in __del__
40     tempdir -- Another object simple stored as an instance variable.
41    
42     As the name suggests the tempdir parameter is intended for a
43     temporary directory the file might be located in. The intended
44     use is that it's an instance of AutoRemoveDir.
45     """
46     self.filename = filename
47     self.tempdir = tempdir
48    
49     def __del__(self, remove = os.remove):
50     remove(self.filename)
51    
52     class AutoRemoveDir:
53    
54     """Remove a directory once all references go away
55    
56     The intended use of this class together with AutoRemoveFile is for
57     temporary directories and files containd therein. An AutoRemoveDir
58     should be instantiated for the directory and passed as the tempdir
59     parameter to every AutoRemoveFile instance created for files in the
60     directory. An AutoRemoveFile shold be instantiated for every file
61     created in the directory so that the directory is automatically
62     removed once the last file is removed.
63     """
64    
65     def __init__(self, filename):
66     """Initialize the AutoRemoveDir
67    
68     The parameter is the name of the directory.
69     """
70     self.filename = filename
71    
72     def __del__(self, rmdir = os.rmdir):
73     rmdir(self.filename)
74    
75    
76     # WeakKey dictionary mapping objects like the transient_db to
77     # AutoRemoveDir or AutoRemoveFile instances to make sure that the
78     # temporary files and the directory are deleted but not before the
79     # objects that use them go away.
80     auto_remover = weakref.WeakKeyDictionary()
81    
82 bh 6 class Session(TitledObject, Modifiable):
83    
84     """A complete session.
85    
86 jan 197 A Session consists of arbitrary numbers of maps, tables and extensions
87 bh 6
88     Session objects send the following events:
89    
90     TITLE_CHANGED -- The title has changed. Parameters: the session.
91    
92     FILENAME_CHANGED -- The filename has changed. No parameters.
93    
94     MAPS_CHANGED -- Maps were added, removed.
95    
96 jan 197 EXTENSIONS_CHANGED -- Extensions were added, removed.
97    
98 jonathan 548 MAP_LAYERS_CHANGED -- Same as the map's event of the same name.
99 bh 6 It's simply resent from the session to make
100     subscriptions easier.
101 bh 232
102     CHANGED -- Generic changed event. Parameters: the session. The
103     event is always issued when any other changed event
104     is issused. This is useful for code that needs to be
105     notified whenever something in the session has
106     changed but it's too cumbersome or error-prone to
107     subscribe to all the individual events.
108 bh 6 """
109    
110 bh 128 # message channels that have to be forwarded from maps contained in
111     # the session.
112     forwarded_channels = (
113 bh 318 # generic channels
114     CHANGED,
115    
116 bh 128 # map specific channels
117     MAP_PROJECTION_CHANGED,
118 jonathan 548 MAP_LAYERS_CHANGED,
119 bh 128
120     # layer channels forwarded by the map
121     LAYER_PROJECTION_CHANGED,
122 jonathan 582 LAYER_CHANGED,
123 jan 197 LAYER_VISIBILITY_CHANGED,
124 bh 128
125 jan 197 # channels forwarded by an extension
126     EXTENSION_CHANGED,
127     EXTENSION_OBJECTS_CHANGED)
128    
129 bh 6 def __init__(self, title):
130     TitledObject.__init__(self, title)
131     Modifiable.__init__(self)
132     self.filename = None
133     self.maps = []
134     self.tables = []
135 bh 851 self.shapestores = []
136 jan 197 self.extensions = []
137 bh 1607 self.db_connections = []
138 bh 765 self.temp_dir = None
139     self.transient_db = None
140 bh 6
141 bh 232 def changed(self, channel = None, *args):
142     """Like the inherited version but issue a CHANGED message as well.
143    
144     The CHANGED message is only issued if channel given is a
145     different channel than CHANGED.
146     """
147     Modifiable.changed(self, channel, *args)
148     if channel != CHANGED:
149     self.issue(CHANGED, self)
150    
151 bh 6 def SetFilename(self, filename):
152     self.filename = filename
153     self.changed(FILENAME_CHANGED)
154    
155     def Maps(self):
156     return self.maps
157    
158     def HasMaps(self):
159     return len(self.maps) > 0
160    
161     def AddMap(self, map):
162     self.maps.append(map)
163 bh 128 for channel in self.forwarded_channels:
164 bh 6 map.Subscribe(channel, self.forward, channel)
165     self.changed(MAPS_CHANGED)
166    
167 bh 241 def RemoveMap(self, map):
168     for channel in self.forwarded_channels:
169     map.Unsubscribe(channel, self.forward, channel)
170     self.maps.remove(map)
171     self.changed(MAPS_CHANGED)
172     map.Destroy()
173    
174 jan 197 def Extensions(self):
175     return self.extensions
176    
177     def HasExtensions(self):
178     return len(self.extensions) > 0
179    
180     def AddExtension(self, extension):
181     self.extensions.append(extension)
182     for channel in self.forwarded_channels:
183     extension.Subscribe(channel, self.forward, channel)
184     self.changed(EXTENSIONS_CHANGED)
185    
186 bh 851 def ShapeStores(self):
187     """Return a list of all ShapeStore objects open in the session"""
188     return [store() for store in self.shapestores]
189    
190     def _add_shapestore(self, store):
191     """Internal: Add the shapestore to the list of shapestores"""
192     self.shapestores.append(weakref.ref(store,
193     self._clean_weak_store_refs))
194    
195     def _clean_weak_store_refs(self, weakref):
196     """Internal: Remove the weakref from the shapestores list"""
197     self.shapestores = [store for store in self.shapestores
198     if store is not weakref]
199    
200     def Tables(self):
201 bh 982 """Return a list of all table objects open in the session
202 bh 851
203 bh 982 The list includes all tables that are indirectly opened through
204     shape stores and the tables that have been opened explicitly.
205     """
206 bh 1282 tables = self.tables[:]
207     ids = {}
208     for t in tables:
209     ids[id(t)] = 1
210     for store in self.ShapeStores():
211     t = store.Table()
212     if id(t) not in ids:
213     ids[id(t)] = 1
214     tables.append(t)
215     return tables
216 bh 982
217 bh 1068 def UnreferencedTables(self):
218     """Return the tables that are not referenced by other data sources"""
219     known = {}
220     for table in self.tables:
221     known[id(table)] = 0
222     for table in self.tables + self.ShapeStores():
223     for dep in table.Dependencies():
224     known[id(dep)] = 1
225     return [table for table in self.tables if known[id(table)] == 0]
226    
227 bh 982 def AddTable(self, table):
228     """Add the table to the session
229    
230     All tables associated with the session that are not implicitly
231     created by the OpenShapefile method (and maybe other Open*
232     methods in the future) have to be passed to this method to make
233     sure the session knows about it. The session keeps a reference
234     to the table. Only tables managed by the session in this way
235     should be used for layers contained in one of the session's
236     maps.
237    
238     The table parameter may be any object implementing the table
239     interface. If it's not already one of the transient tables
240     instantiate an AutoTransientTable with it and use that instead
241     of the original table (note that the AutoTransientTable keeps a
242     reference to the original table).
243    
244     Return the table object actually used by the session.
245     """
246     if not hasattr(table, "transient_table"):
247     transient_table = AutoTransientTable(self.TransientDB(), table)
248     else:
249     transient_table = table
250     self.tables.append(transient_table)
251 bh 1124 self.changed()
252 bh 982 return transient_table
253    
254 bh 987 def RemoveTable(self, table):
255     """Remove the table from the session.
256    
257     The table object must be a table object previously returned by
258     the AddTable method. If the table is not part of the session
259     raise a ValueError.
260 bh 1068
261     Issue a TABLE_REMOVED message after the table has been removed.
262     The message has the removed table as the single parameter.
263 bh 987 """
264     tables = [t for t in self.tables if t is not table]
265     if len(tables) == len(self.tables):
266     raise ValueError
267     self.tables = tables
268 bh 1068 self.changed(TABLE_REMOVED, table)
269 bh 987
270 bh 1268 def DataContainers(self):
271     """Return all data containers, i.e. shapestores and tables"""
272     return self.tables + self.ShapeStores()
273    
274 bh 1036 def OpenTableFile(self, filename):
275     """Open the table file filename and return the table object.
276    
277     The filename argument must be the name of a DBF file.
278     """
279     return self.AddTable(DBFTable(filename))
280    
281 bh 765 def temp_directory(self):
282     """
283     Return the name of the directory for session specific temporary files
284    
285     Create the directory if it doesn't exist yet.
286     """
287     if self.temp_dir is None:
288     temp_dir = mktemp()
289     os.mkdir(temp_dir, 0700)
290     self.temp_dir = temp_dir
291     self.temp_dir_remover = AutoRemoveDir(self.temp_dir)
292     return self.temp_dir
293    
294 bh 723 def OpenShapefile(self, filename):
295     """Return a shapefile store object for the data in the given file"""
296 bh 851 store = ShapefileStore(self, filename)
297     self._add_shapestore(store)
298     return store
299 bh 723
300 bh 1016 def AddShapeStore(self, shapestore):
301     """Add the shapestore to the session.
302    
303     The session only holds a weak reference to the shapestore, so it
304     will automatically be removed from the session when the last
305     reference goes away.
306     """
307     self._add_shapestore(shapestore)
308     return shapestore
309    
310 bh 765 def TransientDB(self):
311     if self.transient_db is None:
312     filename = os.path.join(self.temp_directory(), "transientdb")
313     self.transient_db = TransientDatabase(filename)
314     #print self.temp_dir_remover
315     auto_remover[self.transient_db] = AutoRemoveFile(filename,
316     self.temp_dir_remover)
317     return self.transient_db
318    
319 bh 1607 def AddDBConnection(self, dbconn):
320     """Add the database connection dbconn to the session
321    
322     The argument should be an instance of PostGISConnection.
323     """
324     self.db_connections.append(dbconn)
325     self.changed(DBCONN_ADDED)
326    
327     def DBConnections(self):
328     """
329     Return a list of all database connections registered with the session
330     """
331     return self.db_connections
332    
333     def HasDBConnections(self):
334     """Return whether the session has open database connections"""
335     return bool(self.db_connections)
336    
337     def CanRemoveDBConnection(self, dbconn):
338     """Return whether the database connections dbconn can be removed
339    
340     If can be removed if none of the shapestores or tables in the
341     session references it.
342     """
343     for store in self.ShapeStores():
344     if (isinstance(store, postgisdb.PostGISShapeStore)
345     and store.db is dbconn):
346     return 0
347     for table in self.Tables():
348     if (isinstance(table, postgisdb.PostGISTable)
349     and table.db is dbconn):
350     return 0
351     return 1
352    
353     def RemoveDBConnection(self, dbconn):
354     """Remove the database connection from the session
355    
356     The parameter must be a connection that was registered
357     previously by a AddDBConnection() call.
358     """
359     if self.CanRemoveDBConnection(dbconn):
360     remaining = [c for c in self.db_connections if c is not dbconn]
361     if len(remaining) < len(self.db_connections):
362     self.db_connections = remaining
363     self.changed(DBCONN_REMOVED)
364     else:
365     raise ValueError("DBConection %r is not registered"
366     " with session %r" % (dbconn, self))
367     else:
368     raise ValueError("DBConnection %r is still in use" % (dbconn,))
369    
370 bh 2102 def OpenDBShapeStore(self, db, tablename, id_column = None,
371     geometry_column = None):
372 bh 1607 """Create and return a shapstore for a table in the database
373    
374     The db parameter must be a database connection previously passed
375     to AddDBConnection().
376     """
377 bh 2102 store = postgisdb.PostGISShapeStore(db, tablename,
378     id_column = id_column,
379     geometry_column = geometry_column)
380 bh 1607 self._add_shapestore(store)
381     return store
382    
383 bh 6 def Destroy(self):
384     for map in self.maps:
385     map.Destroy()
386     self.maps = []
387     self.tables = []
388 bh 250 Modifiable.Destroy(self)
389 bh 6
390 bh 778 # Close the transient DB explicitly so that it removes any
391     # journal files from the temporary directory
392     if self.transient_db is not None:
393     self.transient_db.close()
394    
395 bh 6 def forward(self, *args):
396 bh 232 """Reissue events.
397    
398     If the channel the event is forwarded to is a changed-channel
399     that is not the CHANGED channel issue CHANGED as well. An
400     channel is considered to be a changed-channel if it's name ends
401     with 'CHANGED'.
402     """
403 bh 6 if len(args) > 1:
404     args = (args[-1],) + args[:-1]
405     apply(self.issue, args)
406 bh 232 channel = args[0]
407     # It's a bit of a kludge to rely on the channel name for this.
408     if channel.endswith("CHANGED") and channel != CHANGED:
409     self.issue(CHANGED, self)
410 bh 6
411     def WasModified(self):
412     """Return true if the session or one of the maps was modified"""
413     if self.modified:
414     return 1
415     else:
416     for map in self.maps:
417     if map.WasModified():
418     return 1
419     return 0
420    
421     def UnsetModified(self):
422     """Unset the modified flag of the session and the maps"""
423     Modifiable.UnsetModified(self)
424     for map in self.maps:
425     map.UnsetModified()
426    
427 bh 217 def TreeInfo(self):
428     items = []
429     if self.filename is None:
430 jan 374 items.append(_("Filename:"))
431 bh 217 else:
432 jan 374 items.append(_("Filename: %s") % self.filename)
433 bh 6
434 bh 217 if self.WasModified():
435 jan 374 items.append(_("Modified"))
436 bh 217 else:
437 jan 374 items.append(_("Unmodified"))
438 bh 6
439 bh 217 items.extend(self.maps)
440     items.extend(self.extensions)
441    
442 jan 374 return (_("Session: %s") % self.title, items)
443 bh 217
444    
445 bh 6 def create_empty_session():
446     """Return an empty session useful as a starting point"""
447 jan 103 import os
448 jan 374 session = Session(_('unnamed session'))
449 jan 103 session.SetFilename(None)
450 jan 374 session.AddMap(Map(_('unnamed map')))
451 bh 56 session.UnsetModified()
452 bh 6 return session

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26