3030value being a list of keys of the parents of that file. This dictionary stores
3131all the ancestry relationships for the whole lineage.
3232
33+ History in VRT files
34+ --------------------
35+ A GDAL VRT file is handled as a somewhat special case. The component files
36+ of the VRT are treated as parents of the VRT (and there can be no other parents),
37+ and the history of those files is read directly from them, rather than being
38+ copied into the VRT. This is handled transparently, so that when history
39+ is read from the VRT, it appears to have all come from there. This allows the
40+ history of the components to be as dynamic as the data itself.
41+
3342"""
3443import sys
3544import os
5463PARENTS_BY_KEY = "parentsByKey"
5564AUTOENVVARSLIST_NAME = "HISTORY_ENVVARS_TO_AUTOINCLUDE"
5665NO_TIMESTAMP = "UnknownTimestamp"
66+ TIMESTAMP = "timestamp"
5767
5868# These GDAL drivers are known to have limits on the size of metadata which
5969# can be stored, and so we need to keep below these, or we lose everything.
@@ -70,9 +80,36 @@ def __init__(self):
7080 self .metadataByKey = {}
7181 self .parentsByKey = {}
7282
83+ def addParentHistory (self , parentfile ):
84+ """
85+ Add history from parent file to self
86+ """
87+ parentHist = readHistoryFromFile (filename = parentfile )
88+
89+ if parentHist is not None :
90+ key = (os .path .basename (parentfile ),
91+ parentHist .metadataByKey [CURRENTFILE_KEY ][TIMESTAMP ])
92+
93+ # Convert parent's "currentfile" metadata and parentage to normal key entries
94+ self .metadataByKey [key ] = parentHist .metadataByKey [CURRENTFILE_KEY ]
95+ self .parentsByKey [key ] = parentHist .parentsByKey [CURRENTFILE_KEY ]
96+
97+ # Remove those from parentHist
98+ parentHist .metadataByKey .pop (CURRENTFILE_KEY )
99+ parentHist .parentsByKey .pop (CURRENTFILE_KEY )
100+
101+ # Copy over all the other ancestor metadata and parentage
102+ self .metadataByKey .update (parentHist .metadataByKey )
103+ self .parentsByKey .update (parentHist .parentsByKey )
104+ else :
105+ key = (os .path .basename (parentfile ), NO_TIMESTAMP )
106+
107+ # Add this parent as parent of current file
108+ self .parentsByKey [CURRENTFILE_KEY ].append (key )
109+
73110 def toJSON (self ):
74111 """
75- Return a JSON representation of the given ProcessingHistory
112+ Return a JSON representation of the current ProcessingHistory
76113 """
77114 d = {
78115 METADATA_BY_KEY : {},
@@ -131,7 +168,7 @@ def makeAutomaticFields():
131168 dictn = {}
132169
133170 # Time stamp formatted as per ISO 8601 standard, including time zone offset
134- dictn ['timestamp' ] = time .strftime ("%Y-%m-%d %H:%M:%S%z" , time .localtime ())
171+ dictn [TIMESTAMP ] = time .strftime ("%Y-%m-%d %H:%M:%S%z" , time .localtime ())
135172
136173 dictn ['login' ] = getpass .getuser ()
137174
@@ -239,8 +276,6 @@ def writeHistoryToFile(userDict={}, parents=[], *, filename=None, gdalDS=None):
239276 File can be specified as either a filename string or an open GDAL Dataset
240277
241278 """
242- procHist = makeProcessingHistory (userDict , parents )
243-
244279 if filename is not None :
245280 ds = gdal .Open (filename , gdal .GA_Update )
246281 else :
@@ -250,6 +285,12 @@ def writeHistoryToFile(userDict={}, parents=[], *, filename=None, gdalDS=None):
250285 raise ProcessingHistoryError ("Must supply either filename or gdalDS" )
251286
252287 drvrName = ds .GetDriver ().ShortName
288+ isVRT = (drvrName == "VRT" )
289+ if isVRT and len (parents ) > 0 :
290+ msg = "History for VRT files should not have parents"
291+ raise ProcessingHistoryError (msg )
292+
293+ procHist = makeProcessingHistory (userDict , parents )
253294
254295 # Convert to JSON
255296 procHistJSON = procHist .toJSON ()
@@ -295,28 +336,7 @@ def makeProcessingHistory(userDict, parents):
295336 # Now add history from each parent file
296337 procHist .parentsByKey [CURRENTFILE_KEY ] = []
297338 for parentfile in parents :
298- parentHist = readHistoryFromFile (filename = parentfile )
299-
300- if parentHist is not None :
301- key = (os .path .basename (parentfile ),
302- parentHist .metadataByKey [CURRENTFILE_KEY ]['timestamp' ])
303-
304- # Convert parent's "currentfile" metadata and parentage to normal key entries
305- procHist .metadataByKey [key ] = parentHist .metadataByKey [CURRENTFILE_KEY ]
306- procHist .parentsByKey [key ] = parentHist .parentsByKey [CURRENTFILE_KEY ]
307-
308- # Remove those from parentHist
309- parentHist .metadataByKey .pop (CURRENTFILE_KEY )
310- parentHist .parentsByKey .pop (CURRENTFILE_KEY )
311-
312- # Copy over all the other ancestor metadata and parentage
313- procHist .metadataByKey .update (parentHist .metadataByKey )
314- procHist .parentsByKey .update (parentHist .parentsByKey )
315- else :
316- key = (os .path .basename (parentfile ), NO_TIMESTAMP )
317-
318- # Add this parent as parent of current file
319- procHist .parentsByKey [CURRENTFILE_KEY ].append (key )
339+ procHist .addParentHistory (parentfile )
320340
321341 return procHist
322342
@@ -342,6 +362,20 @@ def readHistoryFromFile(filename=None, gdalDS=None):
342362
343363 if procHistJSON is not None :
344364 procHist = ProcessingHistory .fromJSON (procHistJSON )
365+
366+ # If this is a VRT, then read the component files as though they were
367+ # parent files
368+ isVRT = (ds .GetDriver ().ShortName == "VRT" )
369+ if isVRT :
370+ vrtFile = ds .GetDescription ()
371+ componentList = [fn for fn in ds .GetFileList () if fn != vrtFile ]
372+ for componentFile in componentList :
373+ if not os .path .exists (componentFile ):
374+ msg = (f"VRT file '{ vrtFile } ' missing component " +
375+ f"'{ componentFile } '" )
376+ raise ProcessingHistoryError (msg )
377+
378+ procHist .addParentHistory (componentFile )
345379 else :
346380 procHist = None
347381
0 commit comments