/usr/lib/thuban/Thuban/Model/session.py is in thuban 1.2.2-5.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
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 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 | # Copyright (c) 2001, 2002, 2003, 2004 by Intevation GmbH
# Authors:
# Bernhard Herzog <bh@intevation.de>
# Jan-Oliver Wagner <jan@intevation.de>
#
# This program is free software under the GPL (>=v2)
# Read the file COPYING coming with Thuban for details.
__version__ = "$Revision: 2102 $"
import os
from tempfile import mktemp
import weakref
from messages import MAPS_CHANGED, EXTENSIONS_CHANGED, FILENAME_CHANGED, \
MAP_LAYERS_CHANGED, MAP_PROJECTION_CHANGED, \
LAYER_CHANGED, LAYER_PROJECTION_CHANGED, LAYER_VISIBILITY_CHANGED,\
EXTENSION_CHANGED, EXTENSION_OBJECTS_CHANGED, CHANGED, \
TABLE_REMOVED, DBCONN_ADDED, DBCONN_REMOVED
from Thuban import _
from base import TitledObject, Modifiable
from map import Map
from data import ShapefileStore
from table import DBFTable
import postgisdb
from transientdb import TransientDatabase, AutoTransientTable
class AutoRemoveFile:
"""Remove a file once all references go away."""
def __init__(self, filename, tempdir = None):
"""Initialize the AutoRemoveFile
Parameters:
filename -- The name of the file to remove in __del__
tempdir -- Another object simple stored as an instance variable.
As the name suggests the tempdir parameter is intended for a
temporary directory the file might be located in. The intended
use is that it's an instance of AutoRemoveDir.
"""
self.filename = filename
self.tempdir = tempdir
def __del__(self, remove = os.remove):
remove(self.filename)
class AutoRemoveDir:
"""Remove a directory once all references go away
The intended use of this class together with AutoRemoveFile is for
temporary directories and files containd therein. An AutoRemoveDir
should be instantiated for the directory and passed as the tempdir
parameter to every AutoRemoveFile instance created for files in the
directory. An AutoRemoveFile shold be instantiated for every file
created in the directory so that the directory is automatically
removed once the last file is removed.
"""
def __init__(self, filename):
"""Initialize the AutoRemoveDir
The parameter is the name of the directory.
"""
self.filename = filename
def __del__(self, rmdir = os.rmdir):
rmdir(self.filename)
# WeakKey dictionary mapping objects like the transient_db to
# AutoRemoveDir or AutoRemoveFile instances to make sure that the
# temporary files and the directory are deleted but not before the
# objects that use them go away.
auto_remover = weakref.WeakKeyDictionary()
class Session(TitledObject, Modifiable):
"""A complete session.
A Session consists of arbitrary numbers of maps, tables and extensions
Session objects send the following events:
TITLE_CHANGED -- The title has changed. Parameters: the session.
FILENAME_CHANGED -- The filename has changed. No parameters.
MAPS_CHANGED -- Maps were added, removed.
EXTENSIONS_CHANGED -- Extensions were added, removed.
MAP_LAYERS_CHANGED -- Same as the map's event of the same name.
It's simply resent from the session to make
subscriptions easier.
CHANGED -- Generic changed event. Parameters: the session. The
event is always issued when any other changed event
is issused. This is useful for code that needs to be
notified whenever something in the session has
changed but it's too cumbersome or error-prone to
subscribe to all the individual events.
"""
# message channels that have to be forwarded from maps contained in
# the session.
forwarded_channels = (
# generic channels
CHANGED,
# map specific channels
MAP_PROJECTION_CHANGED,
MAP_LAYERS_CHANGED,
# layer channels forwarded by the map
LAYER_PROJECTION_CHANGED,
LAYER_CHANGED,
LAYER_VISIBILITY_CHANGED,
# channels forwarded by an extension
EXTENSION_CHANGED,
EXTENSION_OBJECTS_CHANGED)
def __init__(self, title):
TitledObject.__init__(self, title)
Modifiable.__init__(self)
self.filename = None
self.maps = []
self.tables = []
self.shapestores = []
self.extensions = []
self.db_connections = []
self.temp_dir = None
self.transient_db = None
def changed(self, channel = None, *args):
"""Like the inherited version but issue a CHANGED message as well.
The CHANGED message is only issued if channel given is a
different channel than CHANGED.
"""
Modifiable.changed(self, channel, *args)
if channel != CHANGED:
self.issue(CHANGED, self)
def SetFilename(self, filename):
self.filename = filename
self.changed(FILENAME_CHANGED)
def Maps(self):
return self.maps
def HasMaps(self):
return len(self.maps) > 0
def AddMap(self, map):
self.maps.append(map)
for channel in self.forwarded_channels:
map.Subscribe(channel, self.forward, channel)
self.changed(MAPS_CHANGED)
def RemoveMap(self, map):
for channel in self.forwarded_channels:
map.Unsubscribe(channel, self.forward, channel)
self.maps.remove(map)
self.changed(MAPS_CHANGED)
map.Destroy()
def Extensions(self):
return self.extensions
def HasExtensions(self):
return len(self.extensions) > 0
def AddExtension(self, extension):
self.extensions.append(extension)
for channel in self.forwarded_channels:
extension.Subscribe(channel, self.forward, channel)
self.changed(EXTENSIONS_CHANGED)
def ShapeStores(self):
"""Return a list of all ShapeStore objects open in the session"""
return [store() for store in self.shapestores]
def _add_shapestore(self, store):
"""Internal: Add the shapestore to the list of shapestores"""
self.shapestores.append(weakref.ref(store,
self._clean_weak_store_refs))
def _clean_weak_store_refs(self, weakref):
"""Internal: Remove the weakref from the shapestores list"""
self.shapestores = [store for store in self.shapestores
if store is not weakref]
def Tables(self):
"""Return a list of all table objects open in the session
The list includes all tables that are indirectly opened through
shape stores and the tables that have been opened explicitly.
"""
tables = self.tables[:]
ids = {}
for t in tables:
ids[id(t)] = 1
for store in self.ShapeStores():
t = store.Table()
if id(t) not in ids:
ids[id(t)] = 1
tables.append(t)
return tables
def UnreferencedTables(self):
"""Return the tables that are not referenced by other data sources"""
known = {}
for table in self.tables:
known[id(table)] = 0
for table in self.tables + self.ShapeStores():
for dep in table.Dependencies():
known[id(dep)] = 1
return [table for table in self.tables if known[id(table)] == 0]
def AddTable(self, table):
"""Add the table to the session
All tables associated with the session that are not implicitly
created by the OpenShapefile method (and maybe other Open*
methods in the future) have to be passed to this method to make
sure the session knows about it. The session keeps a reference
to the table. Only tables managed by the session in this way
should be used for layers contained in one of the session's
maps.
The table parameter may be any object implementing the table
interface. If it's not already one of the transient tables
instantiate an AutoTransientTable with it and use that instead
of the original table (note that the AutoTransientTable keeps a
reference to the original table).
Return the table object actually used by the session.
"""
if not hasattr(table, "transient_table"):
transient_table = AutoTransientTable(self.TransientDB(), table)
else:
transient_table = table
self.tables.append(transient_table)
self.changed()
return transient_table
def RemoveTable(self, table):
"""Remove the table from the session.
The table object must be a table object previously returned by
the AddTable method. If the table is not part of the session
raise a ValueError.
Issue a TABLE_REMOVED message after the table has been removed.
The message has the removed table as the single parameter.
"""
tables = [t for t in self.tables if t is not table]
if len(tables) == len(self.tables):
raise ValueError
self.tables = tables
self.changed(TABLE_REMOVED, table)
def DataContainers(self):
"""Return all data containers, i.e. shapestores and tables"""
return self.tables + self.ShapeStores()
def OpenTableFile(self, filename):
"""Open the table file filename and return the table object.
The filename argument must be the name of a DBF file.
"""
return self.AddTable(DBFTable(filename))
def temp_directory(self):
"""
Return the name of the directory for session specific temporary files
Create the directory if it doesn't exist yet.
"""
if self.temp_dir is None:
temp_dir = mktemp()
os.mkdir(temp_dir, 0700)
self.temp_dir = temp_dir
self.temp_dir_remover = AutoRemoveDir(self.temp_dir)
return self.temp_dir
def OpenShapefile(self, filename):
"""Return a shapefile store object for the data in the given file"""
store = ShapefileStore(self, filename)
self._add_shapestore(store)
return store
def AddShapeStore(self, shapestore):
"""Add the shapestore to the session.
The session only holds a weak reference to the shapestore, so it
will automatically be removed from the session when the last
reference goes away.
"""
self._add_shapestore(shapestore)
return shapestore
def TransientDB(self):
if self.transient_db is None:
filename = os.path.join(self.temp_directory(), "transientdb")
self.transient_db = TransientDatabase(filename)
#print self.temp_dir_remover
auto_remover[self.transient_db] = AutoRemoveFile(filename,
self.temp_dir_remover)
return self.transient_db
def AddDBConnection(self, dbconn):
"""Add the database connection dbconn to the session
The argument should be an instance of PostGISConnection.
"""
self.db_connections.append(dbconn)
self.changed(DBCONN_ADDED)
def DBConnections(self):
"""
Return a list of all database connections registered with the session
"""
return self.db_connections
def HasDBConnections(self):
"""Return whether the session has open database connections"""
return bool(self.db_connections)
def CanRemoveDBConnection(self, dbconn):
"""Return whether the database connections dbconn can be removed
If can be removed if none of the shapestores or tables in the
session references it.
"""
for store in self.ShapeStores():
if (isinstance(store, postgisdb.PostGISShapeStore)
and store.db is dbconn):
return 0
for table in self.Tables():
if (isinstance(table, postgisdb.PostGISTable)
and table.db is dbconn):
return 0
return 1
def RemoveDBConnection(self, dbconn):
"""Remove the database connection from the session
The parameter must be a connection that was registered
previously by a AddDBConnection() call.
"""
if self.CanRemoveDBConnection(dbconn):
remaining = [c for c in self.db_connections if c is not dbconn]
if len(remaining) < len(self.db_connections):
self.db_connections = remaining
self.changed(DBCONN_REMOVED)
else:
raise ValueError("DBConection %r is not registered"
" with session %r" % (dbconn, self))
else:
raise ValueError("DBConnection %r is still in use" % (dbconn,))
def OpenDBShapeStore(self, db, tablename, id_column = None,
geometry_column = None):
"""Create and return a shapstore for a table in the database
The db parameter must be a database connection previously passed
to AddDBConnection().
"""
store = postgisdb.PostGISShapeStore(db, tablename,
id_column = id_column,
geometry_column = geometry_column)
self._add_shapestore(store)
return store
def Destroy(self):
for map in self.maps:
map.Destroy()
self.maps = []
self.tables = []
Modifiable.Destroy(self)
# Close the transient DB explicitly so that it removes any
# journal files from the temporary directory
if self.transient_db is not None:
self.transient_db.close()
def forward(self, *args):
"""Reissue events.
If the channel the event is forwarded to is a changed-channel
that is not the CHANGED channel issue CHANGED as well. An
channel is considered to be a changed-channel if it's name ends
with 'CHANGED'.
"""
if len(args) > 1:
args = (args[-1],) + args[:-1]
apply(self.issue, args)
channel = args[0]
# It's a bit of a kludge to rely on the channel name for this.
if channel.endswith("CHANGED") and channel != CHANGED:
self.issue(CHANGED, self)
def WasModified(self):
"""Return true if the session or one of the maps was modified"""
if self.modified:
return 1
else:
for map in self.maps:
if map.WasModified():
return 1
return 0
def UnsetModified(self):
"""Unset the modified flag of the session and the maps"""
Modifiable.UnsetModified(self)
for map in self.maps:
map.UnsetModified()
def TreeInfo(self):
items = []
if self.filename is None:
items.append(_("Filename:"))
else:
items.append(_("Filename: %s") % self.filename)
if self.WasModified():
items.append(_("Modified"))
else:
items.append(_("Unmodified"))
items.extend(self.maps)
items.extend(self.extensions)
return (_("Session: %s") % self.title, items)
def create_empty_session():
"""Return an empty session useful as a starting point"""
import os
session = Session(_('unnamed session'))
session.SetFilename(None)
session.AddMap(Map(_('unnamed map')))
session.UnsetModified()
return session
|