Package conduit :: Package datatypes :: Module File
[hide private]

Source Code for Module conduit.datatypes.File

  1  import os 
  2  import tempfile 
  3  import datetime 
  4  import traceback 
  5  import logging 
  6  log = logging.getLogger("datatypes.File") 
  7   
  8  try: 
  9      import gnomevfs 
 10  except ImportError: 
 11      from gnome import gnomevfs # for maemo 
 12   
 13  import conduit 
 14  import conduit.datatypes.DataType as DataType 
 15  import conduit.Vfs as Vfs 
 16   
17 -class FileTransferError(Exception):
18 pass
19
20 -class File(DataType.DataType):
21 22 _name_ = "file" 23
24 - def __init__(self, URI, **kwargs):
25 """ 26 File constructor. 27 Compulsory args 28 - URI: The title of the note 29 30 Optional kwargs 31 - basepath: The files basepath 32 - group: A named group to which this file belongs 33 """ 34 DataType.DataType.__init__(self) 35 #compulsory args 36 self.URI = gnomevfs.URI(URI) 37 38 #optional args 39 self.basePath = kwargs.get("basepath","") 40 self.group = kwargs.get("group","") 41 42 #instance 43 self.fileInfo = None 44 self.fileExists = False 45 self.triedOpen = False 46 self._newFilename = None 47 self._newMtime = None 48 49 self._isProxyFile = False 50 self._proxyFileSize = None
51
52 - def _open_file(self):
53 if self.triedOpen == False: 54 self.triedOpen = True 55 self.fileExists = gnomevfs.exists(self.URI)
56
57 - def _close_file(self):
58 log.debug("Closing file") 59 self.fileInfo = None 60 self.fileExists = False 61 self.triedOpen = False 62 63 #check to see if we have applied the rename/mtimes yet 64 if self.get_filename() == self._newFilename: 65 log.debug("Clearing pending rename") 66 self._newFilename = None 67 if self.get_mtime() == self._newMtime: 68 log.debug("Clearing pending mtime") 69 self._newMtime = None
70
72 return conduit.GLOBALS.cancelled
73
74 - def _xfer_progress_callback(self, info, cancel_func):
75 #check if cancelled 76 try: 77 if cancel_func(): 78 log.info("Transfer of %s -> %s cancelled" % (info.source_name, info.target_name)) 79 return 0 80 except Exception, ex: 81 log.warn("Could not call gnomevfs cancel function") 82 return 0 83 return True
84
85 - def _get_text_uri(self):
86 """ 87 The mixing of text_uri and gnomevfs.URI in the gnomevfs api is very 88 annoying. This function returns the full text uri for the file 89 """ 90 return str(self.URI)
91
92 - def _get_file_info(self):
93 """ 94 Gets the file info. Because gnomevfs is dumb this method works a lot 95 more reliably than self.vfsFileHandle.get_file_info(). 96 97 Only tries to get the info once for performance reasons 98 """ 99 self._open_file() 100 #The get_file_info works more reliably on remote vfs shares 101 if self.fileInfo == None: 102 if self.exists() == True: 103 self.fileInfo = gnomevfs.get_file_info(self.URI, gnomevfs.FILE_INFO_DEFAULT) 104 else: 105 log.warn("Cannot get info on non-existant file %s" % self.URI)
106
107 - def _defer_rename(self, filename):
108 """ 109 In the event that the file is on a read-only volume this call defers the 110 file rename till after the transfer proces 111 """ 112 log.debug("Defering rename till transfer (New name: %s)" % filename) 113 self._newFilename = filename
114
115 - def _is_deferred_rename(self):
116 return self._newFilename != None
117
118 - def _defer_new_mtime(self, mtime):
119 """ 120 In the event that the file is on a read-only volume this call defers the 121 file mtime modification till after the transfer proces 122 """ 123 log.debug("Defering new mtime till transfer (New mtime: %s)" % mtime) 124 self._newMtime = mtime
125
126 - def _is_deferred_new_mtime(self):
127 return self._newMtime != None
128
129 - def _is_tempfile(self):
130 tmpdir = tempfile.gettempdir() 131 if self.is_local() and self.URI.path.startswith(tmpdir): 132 return True 133 else: 134 return False
135
136 - def _is_proxyfile(self):
137 return self._isProxyFile
138
139 - def _set_file_mtime(self, mtime):
140 timestamp = conduit.utils.datetime_get_timestamp(mtime) 141 log.debug("Setting mtime of %s to %s (%s)" % (self.URI, timestamp, type(timestamp))) 142 newInfo = gnomevfs.FileInfo() 143 newInfo.mtime = timestamp 144 gnomevfs.set_file_info(self.URI,newInfo,gnomevfs.SET_FILE_INFO_TIME) 145 #close so the file info is re-read 146 self._close_file()
147
148 - def _set_filename(self, filename):
149 newInfo = gnomevfs.FileInfo() 150 151 #FIXME: Gnomevfs complains if name is unicode 152 filename = str(filename) 153 oldname = str(self.get_filename()) 154 155 if filename != oldname: 156 newInfo.name = filename 157 olduri = self._get_text_uri() 158 newuri = olduri.replace(oldname, filename) 159 160 log.debug("Trying to rename file %s (%s) -> %s (%s)" % (olduri,oldname,newuri,filename)) 161 gnomevfs.set_file_info(self.URI,newInfo,gnomevfs.SET_FILE_INFO_NAME) 162 #close so the file info is re-read 163 self.URI = gnomevfs.URI(newuri) 164 self._close_file()
165
166 - def set_from_instance(self, f):
167 """ 168 Function to give this file all the properties of the 169 supplied instance. This is important in converters where there 170 might be pending renames etc on the file that you 171 do not want to lose 172 """ 173 self.URI = f.URI 174 self.basePath = f.basePath 175 self.group = f.group 176 self.fileInfo = f.fileInfo 177 self.fileExists = f.fileExists 178 self.triedOpen = f.triedOpen 179 self._newFilename = f._newFilename 180 self._newMtime = f._newMtime 181 self._isProxyFile = f._isProxyFile 182 self._proxyFileSize = f._proxyFileSize
183
184 - def to_tempfile(self):
185 """ 186 Copies this file to a temporary file in the system tempdir 187 @returns: The local file path 188 """ 189 #Get a temporary file name 190 tempname = tempfile.mkstemp(prefix="conduit")[1] 191 log.debug("Tempfile %s -> %s" % (self.URI, tempname)) 192 filename = self.get_filename() 193 mtime = self.get_mtime() 194 self.transfer( 195 newURIString=tempname, 196 overwrite=True 197 ) 198 #retain all original information 199 self.force_new_filename(filename) 200 self.force_new_mtime(mtime) 201 return tempname
202
203 - def exists(self):
204 self._open_file() 205 return self.fileExists
206
207 - def is_local(self):
208 """ 209 Checks if a File is on the local filesystem or not. If not, it is 210 expected that the caller will call get_local_uri, which will 211 copy the file to that location, and return the new path 212 """ 213 return self.URI.is_local
214
215 - def is_directory(self):
216 """ 217 @returns: True if the File is a directory 218 """ 219 self._get_file_info() 220 return self.fileInfo.type == gnomevfs.FILE_TYPE_DIRECTORY
221
222 - def force_new_filename(self, filename):
223 """ 224 Renames the file 225 """ 226 if self._is_tempfile() or self._is_proxyfile(): 227 self._defer_rename(filename) 228 else: 229 try: 230 self._set_filename(filename) 231 except gnomevfs.NotSupportedError: 232 #dunno what this is 233 self._defer_rename(filename) 234 except gnomevfs.AccessDeniedError: 235 #file is on readonly filesystem 236 self._defer_rename(filename) 237 except gnomevfs.NotPermittedError: 238 #file is on readonly filesystem 239 self._defer_rename(filename) 240 except gnomevfs.FileExistsError: 241 #I think this is when you rename a file to its current name 242 pass
243
244 - def force_new_file_extension(self, ext):
245 """ 246 Changes the file extension to ext. 247 @param ext: The new file extension (including the dot) 248 """ 249 curname,curext = self.get_filename_and_extension() 250 if curext != ext: 251 self.force_new_filename(curname+ext)
252
253 - def force_new_mtime(self, mtime):
254 """ 255 Changes the mtime of the file 256 """ 257 if self._is_tempfile() or self._is_proxyfile(): 258 self._defer_new_mtime(mtime) 259 else: 260 try: 261 self._set_file_mtime(mtime) 262 except gnomevfs.NotSupportedError: 263 #dunno what this is 264 self._defer_new_mtime(mtime) 265 except gnomevfs.AccessDeniedError: 266 #file is on readonly filesystem 267 self._defer_new_mtime(mtime) 268 except gnomevfs.NotPermittedError: 269 #file is on readonly filesystem 270 self._defer_new_mtime(mtime)
271
272 - def transfer(self, newURIString, overwrite=False, cancel_function=None):
273 """ 274 Transfers the file to newURI. Thin wrapper around go_gnomevfs_transfer 275 because it also sets the new info of the file. By wrapping the xfer_uri 276 funtion it gives the ability to cancel transfers 277 278 @type newURIString: C{string} 279 """ 280 #the default cancel function just checks conduit.GLOBALS.cancelled 281 if cancel_function == None: 282 cancel_function = self._xfer_check_global_cancel_flag 283 284 if self._is_deferred_rename(): 285 newURI = gnomevfs.URI(newURIString) 286 #if it exists and its a directory then transfer into that dir 287 #with the new filename 288 if gnomevfs.exists(newURI): 289 info = gnomevfs.get_file_info(newURI, gnomevfs.FILE_INFO_DEFAULT) 290 if info.type == gnomevfs.FILE_TYPE_DIRECTORY: 291 #append the new filename 292 newURI = newURI.append_file_name(self._newFilename) 293 log.debug("Using deferred filename in transfer") 294 else: 295 newURI = gnomevfs.URI(newURIString) 296 297 if overwrite: 298 mode = gnomevfs.XFER_OVERWRITE_MODE_REPLACE 299 else: 300 mode = gnomevfs.XFER_OVERWRITE_MODE_SKIP 301 302 log.debug("Transfering File %s -> %s" % (self.URI, newURI)) 303 304 #recursively create all parent dirs if needed 305 parent = str(newURI.parent) 306 if not gnomevfs.exists(parent): 307 Vfs.uri_make_directory_and_parents(parent) 308 309 #Copy the file 310 try: 311 result = gnomevfs.xfer_uri( 312 source_uri=self.URI, 313 target_uri=newURI, 314 xfer_options=gnomevfs.XFER_NEW_UNIQUE_DIRECTORY, 315 error_mode=gnomevfs.XFER_ERROR_MODE_ABORT, 316 overwrite_mode=mode, 317 progress_callback=self._xfer_progress_callback, 318 data=cancel_function 319 ) 320 except gnomevfs.InterruptedError: 321 raise FileTransferError 322 323 #close the file and the handle so that the file info is refreshed 324 self.URI = newURI 325 self._close_file() 326 327 #if we have been transferred anywhere (i.e. the destination, our 328 #location, is writable) then we are no longer a proxy file 329 self._isProxyFile = False 330 331 #apply any pending renames 332 if self._is_deferred_rename(): 333 self.force_new_filename(self._newFilename) 334 if self._is_deferred_new_mtime(): 335 self.force_new_mtime(self._newMtime)
336
337 - def delete(self):
338 #close the file and the handle so that the file info is refreshed 339 self._close_file() 340 log.debug("Deleting %s" % self.URI) 341 result = gnomevfs.unlink(self.URI)
342
343 - def get_mimetype(self):
344 self._get_file_info() 345 try: 346 return self.fileInfo.mime_type 347 except ValueError: 348 #Why is gnomevfs so stupid and must I do this for local URIs?? 349 return gnomevfs.get_mime_type(self._get_text_uri())
350
351 - def get_mtime(self):
352 """ 353 Returns the modification time for the file 354 355 @returns: A python datetime object containing the modification time 356 of the file or None on error. 357 @rtype: C{datetime} 358 """ 359 if self._is_deferred_new_mtime(): 360 return self._newMtime 361 else: 362 self._get_file_info() 363 try: 364 return datetime.datetime.fromtimestamp(self.fileInfo.mtime) 365 except: 366 return None
367
368 - def set_mtime(self, mtime):
369 """ 370 Sets the modification time of the file 371 """ 372 if mtime != None: 373 try: 374 self.force_new_mtime(mtime) 375 except Exception, err: 376 log.warn("Error setting mtime of %s. \n%s" % (self.URI, traceback.format_exc()))
377
378 - def get_size(self):
379 """ 380 Gets the file size 381 """ 382 if self._is_proxyfile(): 383 return self._proxyFileSize 384 else: 385 self._get_file_info() 386 try: 387 return self.fileInfo.size 388 except: 389 return None
390
391 - def get_hash(self):
392 # Join the tags into a string to be hashed so the object is updated if 393 # they change. 394 tagstr = "".join(self.get_tags()) 395 #FIXME: self.get_size() does not seem reliable 396 return hash(tagstr)
397
398 - def get_filename(self):
399 """ 400 Returns the filename of the file 401 """ 402 if self._is_deferred_rename(): 403 return self._newFilename 404 else: 405 self._get_file_info() 406 return self.fileInfo.name
407
409 """ 410 @returns: filename,file_extension 411 """ 412 return os.path.splitext(self.get_filename())
413
414 - def get_contents_as_text(self):
415 return gnomevfs.read_entire_file(self._get_text_uri())
416
417 - def get_local_uri(self):
418 """ 419 Gets the local URI (full path) for the file. If the file is 420 already on the local system then its local path is returned 421 (excluding the vfs sheme, i.e. file:///foo/bar becomes /foo/bar) 422 423 If it is a remote file then a local temporary file copy is created 424 425 This function is useful for non gnomevfs enabled libs 426 427 @returns: local absolute path the the file or None on error 428 @rtype: C{string} 429 """ 430 if self.is_local(): 431 #FIXME: The following call produces a runtime error if the URI 432 #is malformed. Reason number 37 gnomevfs should die 433 u = gnomevfs.get_local_path_from_uri(self._get_text_uri()) 434 #Backup approach... 435 #u = self.URI[len("file://"):] 436 return u 437 else: 438 return self.to_tempfile()
439
440 - def get_relative_uri(self):
441 """ 442 @returns: The files URI relative to its basepath 443 """ 444 if self.basePath: 445 return Vfs.uri_get_relative(self.basePath,self._get_text_uri()) 446 else: 447 return self._get_text_uri()
448
449 - def compare(self, B, sizeOnly=False):
450 """ 451 Compare me with B based upon their modification times, or optionally 452 based on size only 453 """ 454 if gnomevfs.exists(B.URI) == False: 455 return conduit.datatypes.COMPARISON_NEWER 456 457 #Compare based on size only? 458 if sizeOnly: 459 meSize = self.get_size() 460 bSize = B.get_size() 461 log.debug("Comparing %s (SIZE: %s) with %s (SIZE: %s)" % (self.URI, meSize, B.URI, bSize)) 462 if meSize == None or bSize == None: 463 return conduit.datatypes.COMPARISON_UNKNOWN 464 elif meSize == bSize: 465 return conduit.datatypes.COMPARISON_EQUAL 466 else: 467 return conduit.datatypes.COMPARISON_UNKNOWN 468 469 #Else look at the modification times 470 meTime = self.get_mtime() 471 bTime = B.get_mtime() 472 log.debug("Comparing %s (MTIME: %s) with %s (MTIME: %s)" % (self.URI, meTime, B.URI, bTime)) 473 if meTime is None: 474 return conduit.datatypes.COMPARISON_UNKNOWN 475 if bTime is None: 476 return conduit.datatypes.COMPARISON_UNKNOWN 477 478 #Am I newer than B 479 if meTime > bTime: 480 return conduit.datatypes.COMPARISON_NEWER 481 #Am I older than B? 482 elif meTime < bTime: 483 return conduit.datatypes.COMPARISON_OLDER 484 485 elif meTime == bTime: 486 meSize = self.get_size() 487 bSize = B.get_size() 488 #log.debug("Comparing %s (SIZE: %s) with %s (SIZE: %s)" % (A.URI, meSize, B.URI, bSize)) 489 #If the times are equal, and the sizes are equal then assume 490 #that they are the same. 491 if meSize == None or bSize == None: 492 #In case of error 493 return conduit.datatypes.COMPARISON_UNKNOWN 494 elif meSize == bSize: 495 return conduit.datatypes.COMPARISON_EQUAL 496 else: 497 #shouldnt get here 498 log.warn("Error comparing file sizes") 499 return conduit.datatypes.COMPARISON_UNKNOWN 500 501 else: 502 log.warn("Error comparing file modification times") 503 return conduit.datatypes.COMPARISON_UNKNOWN
504
505 - def __getstate__(self):
506 data = DataType.DataType.__getstate__(self) 507 data['basePath'] = self.basePath 508 data['group'] = self.group 509 data['filename'] = self.get_filename() 510 data['filemtime'] = self.get_mtime() 511 512 #FIXME: Maybe we should tar this first... 513 data['data'] = open(self.get_local_uri(), 'rb').read() 514 515 return data
516
517 - def __setstate__(self, data):
518 fd, name = tempfile.mkstemp(prefix="netsync") 519 os.write(fd, data['data']) 520 os.close(fd) 521 522 self.URI = gnomevfs.URI(name) 523 self.basePath = data['basePath'] 524 self.group = data['group'] 525 self._defer_rename(data['filename']) 526 self._defer_new_mtime(data['filemtime']) 527 528 #Ensure we re-read the fileInfo 529 self.fileInfo = None 530 self.fileExists = False 531 self.triedOpen = False 532 533 DataType.DataType.__setstate__(self, data)
534
535 -class TempFile(File):
536 """ 537 Creates a file in the system temp directory with the given contents. 538 """
539 - def __init__(self, contents=""):
540 #create the file containing contents 541 fd, name = tempfile.mkstemp(prefix="conduit") 542 os.write(fd, contents) 543 os.close(fd) 544 File.__init__(self, name) 545 log.debug("New tempfile created at %s" % name)
546
547 -class ProxyFile(File):
548 """ 549 Pretends to be a file for the sake of comparison and transer. Typically 550 located on a remote, read only resource, such as http://. Once transferred 551 to the local filesystem, it behaves just like a file. 552 """
553 - def __init__(self, URI, name, modified, size, **kwargs):
554 File.__init__(self, URI, **kwargs) 555 556 self._isProxyFile = True 557 self._proxyFileSize = size 558 559 if modified: 560 self.force_new_mtime(modified) 561 if name: 562 self.force_new_filename(name)
563