Changeset View
Standalone View
helpers/helperslib/Packages.py
Show First 20 Lines • Show All 74 Lines • ▼ Show 20 Line(s) | 15 | class Archive(object): | |||
---|---|---|---|---|---|
75 | 75 | | |||
76 | # Retrieve the package for the given project and branch group combination from the archive | 76 | # Retrieve the package for the given project and branch group combination from the archive | ||
77 | # If a cached copy is available and is the most recent version that will be used instead of fetching the package again | 77 | # If a cached copy is available and is the most recent version that will be used instead of fetching the package again | ||
78 | # Where the remote archive has a newer version then the package will be retrieved from the remote archive | 78 | # Where the remote archive has a newer version then the package will be retrieved from the remote archive | ||
79 | # All lookups will be restricted to our current platform, as specified when creating this archive. | 79 | # All lookups will be restricted to our current platform, as specified when creating this archive. | ||
80 | def retrievePackage( self, package ): | 80 | def retrievePackage( self, package ): | ||
81 | # Determine the names the metadata and archive files would have respectively | 81 | # Determine the names the metadata and archive files would have respectively | ||
82 | metadataFilename = package + ".yaml" | 82 | metadataFilename = package + ".yaml" | ||
83 | contentsFilename = package + self.contentsSuffix | 83 | | ||
84 | def contentsFilename(): | ||||
bcooksley: Rather than writing this as a function, could we keep this as variable to reduce the number of… | |||||
well phabricator shows \t as 4 spaces and not as 8. I fixed it to replace everything with \t knauss: well phabricator shows \t as 4 spaces and not as 8. I fixed it to replace everything with \t | |||||
85 | contentsSuffix = localMetadata.get('contentsSuffix', self.contentsSuffix) | ||||
86 | return package + contentsSuffix | ||||
87 | | ||||
88 | def localContentsPath(): | ||||
89 | return os.path.join(self.cacheLocation(), contentsFilename()) | ||||
90 | | ||||
91 | # Does the archive contain what we are after? | ||||
Not sure why this needed to be functionised? bcooksley: Not sure why this needed to be functionised?
(It makes it inconsistent with the… | |||||
Well I functioned this, because the localMetada changes within this method. Otherwise I need to update every time the localContentsPath if we have new metadata. knauss: Well I functioned this, because the localMetada changes within this method. Otherwise I need to… | |||||
92 | # It will never contain it if the use of a local cache has been disabled | ||||
93 | if package not in self.serverManifest: | ||||
94 | # There is nothing for us to fetch - the server will just yield a 404 | ||||
95 | # So let's bow out gracefully here | ||||
96 | return ( None, None ) | ||||
84 | 97 | | |||
85 | # Begin determining if we need to download or not | 98 | # Begin determining if we need to download or not | ||
86 | # We start from the assumption we will need to download an archive | 99 | # We start from the assumption we will need to download an archive | ||
87 | needToDownload = True | 100 | needToDownload = True | ||
88 | 101 | | |||
89 | # Do we have a local copy of this? | 102 | # Do we have a local copy of this? | ||
90 | localMetadataPath = os.path.join(self.cacheLocation(), metadataFilename) | 103 | localMetadataPath = os.path.join(self.cacheLocation(), metadataFilename) | ||
91 | if os.path.exists( localMetadataPath ): | 104 | if os.path.exists( localMetadataPath ): | ||
92 | # Load the local metadata | 105 | # Load the local metadata | ||
93 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | 106 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | ||
94 | localMetadata = yaml.load( localMetadataFile ) | 107 | localMetadata = yaml.load( localMetadataFile ) | ||
95 | # Look it up in the server manifest... | 108 | # Look it up in the server manifest... | ||
96 | serverMetadata = self.serverManifest[ package ] | 109 | serverMetadata = self.serverManifest[ package ] | ||
97 | # If the server timestamp is lower or the same, no need to fetch | 110 | # If the server timestamp is lower or the same, no need to fetch | ||
98 | needToDownload = ( serverMetadata['timestamp'] > localMetadata['timestamp'] ) | 111 | needToDownload = ( serverMetadata['timestamp'] > localMetadata['timestamp'] ) | ||
99 | 112 | | |||
100 | # Does the local contents file exist? | 113 | # Does the local contents file exist? | ||
101 | localContentsPath = os.path.join(self.cacheLocation(), contentsFilename) | 114 | if not os.path.exists( localContentsPath() ): | ||
102 | if not os.path.exists( localContentsPath ): | 115 | # If it doesn't, we always need to download | ||
103 | # If it doesn't, we always need to download | 116 | needToDownload = True | ||
bcooksley: Newline removed? | |||||
104 | needToDownload = True | | |||
105 | | ||||
106 | # Does the archive contain what we are after? | | |||
Why was this removed? The code below will try to download something which we know will never exist without this, and urllib throws exceptions I believe (which will cause failures elsewhere) bcooksley: Why was this removed?
The code below will try to download something which we know will never… | |||||
I moved this part to line94 before the check for search of localMetadata. If it is not inside serverManifest, we don't need to try to find the localMetadata knauss: I moved this part to line94 before the check for search of localMetadata. If it is not inside… | |||||
107 | # It will never contain it if the use of a local cache has been disabled | | |||
108 | if package not in self.serverManifest: | | |||
109 | # There is nothing for us to fetch - the server will just yield a 404 | | |||
110 | # So let's bow out gracefully here | | |||
111 | return ( None, None ) | | |||
112 | 117 | | |||
113 | # Let's retrieve the file if we need to now... | 118 | # Let's retrieve the file if we need to now... | ||
114 | if needToDownload: | 119 | if needToDownload: | ||
115 | # Download the archive first... | 120 | # Download the metadata file... | ||
116 | response = urllib.request.urlopen( self.downloadBaseUrl() + '/' + contentsFilename ) | | |||
117 | latestContent = tempfile.NamedTemporaryFile(delete=False, mode='wb', dir=self.temporaryFileLocation()) | | |||
118 | latestContent.write( response.read() ) | | |||
119 | latestContent.close() | | |||
120 | | ||||
121 | # Now the metadata file... | | |||
122 | response = urllib.request.urlopen( self.downloadBaseUrl() + '/' + metadataFilename ) | 121 | response = urllib.request.urlopen( self.downloadBaseUrl() + '/' + metadataFilename ) | ||
122 | content = response.read() | ||||
bcooksley: You're now reading the response twice? | |||||
123 | latestMetadata = tempfile.NamedTemporaryFile(delete=False, mode='wb', dir=self.temporaryFileLocation()) | 123 | latestMetadata = tempfile.NamedTemporaryFile(delete=False, mode='wb', dir=self.temporaryFileLocation()) | ||
124 | latestMetadata.write( response.read() ) | 124 | latestMetadata.write( response.read() ) | ||
125 | latestMetadata.close() | 125 | latestMetadata.close() | ||
126 | 126 | | |||
127 | # And parse the content so we get the correct contentsSuffix | ||||
128 | localMetadata = yaml.load( content ) | ||||
The server manifest always includes the full content of the individual archive manifest files, so this isn't needed. bcooksley: The server manifest always includes the full content of the individual archive manifest files… | |||||
But the whole function always works with localMetadata. And I think it is better to use localMetadata, as serverManifest is only read once while initalizing the Archive. So localMetadata+content may be newer. knauss: But the whole function always works with localMetadata. And I think it is better to use… | |||||
129 | | ||||
130 | # And now the archive... | ||||
131 | response = urllib.request.urlopen( self.downloadBaseUrl() + '/' + contentsFilename() ) | ||||
132 | latestContent = tempfile.NamedTemporaryFile(delete=False, mode='wb', dir=self.temporaryFileLocation()) | ||||
133 | latestContent.write( response.read() ) | ||||
134 | latestContent.close() | ||||
135 | | ||||
127 | # Move both to their final resting places | 136 | # Move both to their final resting places | ||
128 | shutil.move( latestContent.name, localContentsPath ) | 137 | shutil.move( latestContent.name, localContentsPath() ) | ||
129 | shutil.move( latestMetadata.name, localMetadataPath ) | 138 | shutil.move( latestMetadata.name, localMetadataPath ) | ||
130 | 139 | | |||
131 | # All done, we can return a tuple of the archive and metadata now | 140 | # All done, we can return a tuple of the archive and metadata now | ||
132 | # As we want to return the metadata already parsed (nobody outside this class needs to know it is stored as YAML) we'll load it now | 141 | # As we want to return the metadata already parsed (nobody outside this class needs to know it is stored as YAML) we'll load it now | ||
133 | # The archive is returned as a simple path to the file, which can be passed to tarfile.open() as appropriate | 142 | # The archive is returned as a simple path to the file, which can be passed to tarfile.open() as appropriate | ||
134 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | 143 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | ||
this is replaced by l128, so not needed anymore? Okay there may be issues with move, but than this is raised anyways. knauss: this is replaced by l128, so not needed anymore? Okay there may be issues with move, but than… | |||||
135 | localMetadata = yaml.load( localMetadataFile ) | 144 | localMetadata = yaml.load( localMetadataFile ) | ||
136 | 145 | | |||
137 | return ( localContentsPath, localMetadata ) | 146 | return ( localContentsPath(), localMetadata ) | ||
138 | 147 | | |||
139 | # Generates the metadata which is stored in the .yaml file that accompanies each package which is stored in the archive | 148 | # Generates the metadata which is stored in the .yaml file that accompanies each package which is stored in the archive | ||
140 | # Extra metadata saved to metadata file, and will be written to yaml file, needs to be a dict like object | 149 | # Extra metadata saved to metadata file, and will be written to yaml file, needs to be a dict like object | ||
141 | def generateMetadataForFile( self, contentsNeedingMetadata, scmRevision, extraMetadata=None ): | 150 | def generateMetadataForFile( self, contentsNeedingMetadata, scmRevision, extraMetadata=None ): | ||
142 | # First, determine the timestamp the file was last modified | 151 | # First, determine the timestamp the file was last modified | ||
143 | packageTimestamp = os.path.getmtime( contentsNeedingMetadata ) | 152 | packageTimestamp = os.path.getmtime( contentsNeedingMetadata ) | ||
144 | # Now the checksum | 153 | # Now the checksum | ||
145 | packageChecksum = CommonUtils.generateFileChecksum( contentsNeedingMetadata ) | 154 | packageChecksum = CommonUtils.generateFileChecksum( contentsNeedingMetadata ) | ||
146 | 155 | | |||
147 | # Start preparing the metadata we're going to save alongside the package | 156 | # Start preparing the metadata we're going to save alongside the package | ||
Whitespace change? bcooksley: Whitespace change?
(As an aside, parts of the file above appear to have been reformatted and… | |||||
it is a 8spaces -> \t to be consistent with the rest of the file - I can move this to a single commit, if you want to knauss: it is a 8spaces -> \t to be consistent with the rest of the file - I can move this to a single… | |||||
148 | metadataForPackage = {} | 157 | metadataForPackage = {} | ||
149 | 158 | | |||
150 | # If we have extraMetadata for this Package, then we need to pre-seed the metadata dictionary | 159 | # If we have extraMetadata for this Package, then we need to pre-seed the metadata dictionary | ||
151 | if extraMetadata: | 160 | if extraMetadata: | ||
152 | metadataForPackage = copy.copy( extraMetadata ) | 161 | metadataForPackage = copy.copy( extraMetadata ) | ||
153 | 162 | | |||
154 | # Update/adds the nessary keys, that we want to exist. | 163 | # Update/adds the nessary keys, that we want to exist. | ||
155 | metadataForPackage.update({ | 164 | metadataForPackage.update({ | ||
156 | 'timestamp': packageTimestamp, | 165 | 'timestamp': packageTimestamp, | ||
157 | 'checksum': packageChecksum, | 166 | 'checksum': packageChecksum, | ||
158 | 'scmRevision': scmRevision | 167 | 'scmRevision': scmRevision, | ||
168 | 'contentsSuffix': self.contentsSuffix, | ||||
159 | }) | 169 | }) | ||
160 | 170 | | |||
161 | # Write the YAML out to a temporary file | 171 | # Write the YAML out to a temporary file | ||
162 | latestMetadata = tempfile.NamedTemporaryFile(delete=False, mode='w', dir=self.temporaryFileLocation()) | 172 | latestMetadata = tempfile.NamedTemporaryFile(delete=False, mode='w', dir=self.temporaryFileLocation()) | ||
163 | yaml.dump( metadataForPackage, latestMetadata, default_flow_style=False, allow_unicode=True ) | 173 | yaml.dump( metadataForPackage, latestMetadata, default_flow_style=False, allow_unicode=True ) | ||
164 | latestMetadata.close() | 174 | latestMetadata.close() | ||
165 | 175 | | |||
166 | # Return the name to that temporary file | 176 | # Return the name to that temporary file | ||
▲ Show 20 Lines • Show All 49 Lines • ▼ Show 20 Line(s) | 183 | def storePackage( self, package, archiveFileToInclude, scmRevision = '', extraMetadata=None ): | |||
216 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | 226 | with open(localMetadataPath, 'r', encoding='utf-8') as localMetadataFile: | ||
217 | self.serverManifest[ package ] = yaml.load( localMetadataFile ) | 227 | self.serverManifest[ package ] = yaml.load( localMetadataFile ) | ||
218 | 228 | | |||
219 | # Performs the package publishing process | 229 | # Performs the package publishing process | ||
220 | # This function should only be called on the archive server and will not function correctly on clients. | 230 | # This function should only be called on the archive server and will not function correctly on clients. | ||
221 | def publishPackage( self, package ): | 231 | def publishPackage( self, package ): | ||
222 | # Determine the names the metadata and archive files would have respectively | 232 | # Determine the names the metadata and archive files would have respectively | ||
223 | metadataFilename = package + ".yaml" | 233 | metadataFilename = package + ".yaml" | ||
224 | contentsFilename = package + self.contentsSuffix | 234 | | ||
235 | stagedMetadataPath = os.path.join( self.config['client']['uploadDirectory'], self.platform, metadataFilename ) | ||||
236 | finalMetadataPath = os.path.join( self.config['server']['archiveDirectory'], self.platform, metadataFilename ) | ||||
237 | | ||||
238 | # Get the used contentSuffix use self.contentsSuffix as fallback | ||||
bcooksley: cententSuffix? | |||||
239 | with open(stagedMetadataPath, 'r', encoding='utf-8') as metadataFile | ||||
240 | metadata = yaml.load( metadataFile ) | ||||
241 | contentsSuffix = metadata.get('contentsSuffix', self.contentsSuffix) | ||||
242 | | ||||
Instead of try... except wouldn't it be safer/nicer to do if 'contentsSuffix' in metadata? bcooksley: Instead of try... except wouldn't it be safer/nicer to do if 'contentsSuffix' in metadata?
(I… | |||||
This is a more philosophical discussion. The python way is to use try/except:
if "contentsSuffix" in metadata: contentsSuffix = metadata['ContentsSuffix'] -> but we have someting better ( I often forget about this), that is exactly for our usecase: contentsSuffix = metadata.get("contentsSuffix" , self.contentsSuffix) knauss: This is a more philosophical discussion. The python way is to use try/except:
* it is faster… | |||||
.get() does look like the cleanest and most proper solution yes, let's go with that. bcooksley: .get() does look like the cleanest and most proper solution yes, let's go with that. | |||||
243 | contentsFilename = package + contentsSuffix | ||||
244 | stagedContentsPath = os.path.join( self.config['client']['uploadDirectory'], self.platform, contentsFilename ) | ||||
245 | finalContentsPath = os.path.join( self.config['server']['archiveDirectory'], self.platform, contentsFilename ) | ||||
225 | 246 | | |||
226 | # Move the contents file first | 247 | # Move the contents file first | ||
227 | # Assuming we're on the same file system this should be an atomic operation and thus instant | 248 | # Assuming we're on the same file system this should be an atomic operation and thus instant | ||
228 | # We move the metadata second in case uploadDirectory and archiveDirectory are on different file systems | 249 | # We move the metadata second in case uploadDirectory and archiveDirectory are on different file systems | ||
229 | # As the contents file could be several hundred megabytes, while the metadata file should be a matter of a few kilobytes and thus copy across instantly | 250 | # As the contents file could be several hundred megabytes, while the metadata file should be a matter of a few kilobytes and thus copy across instantly | ||
230 | # Also, as the metadata file governs when files should be expired, it is better to over-expire than risk an outdated cached copy being used | 251 | # Also, as the metadata file governs when files should be expired, it is better to over-expire than risk an outdated cached copy being used | ||
231 | stagedContentsPath = os.path.join( self.config['client']['uploadDirectory'], self.platform, contentsFilename ) | | |||
232 | finalContentsPath = os.path.join( self.config['server']['archiveDirectory'], self.platform, contentsFilename ) | | |||
233 | shutil.move( stagedContentsPath, finalContentsPath ) | 252 | shutil.move( stagedContentsPath, finalContentsPath ) | ||
234 | 253 | | |||
235 | # Now the metadata goes over as well | 254 | # Now the metadata goes over as well | ||
236 | stagedMetadataPath = os.path.join( self.config['client']['uploadDirectory'], self.platform, metadataFilename ) | | |||
237 | finalMetadataPath = os.path.join( self.config['server']['archiveDirectory'], self.platform, metadataFilename ) | | |||
238 | shutil.move( stagedMetadataPath, finalMetadataPath ) | 255 | shutil.move( stagedMetadataPath, finalMetadataPath ) | ||
239 | 256 | | |||
240 | # Now we update the global manifest file for this platform | 257 | # Now we update the global manifest file for this platform | ||
241 | self.refreshManifest() | 258 | self.refreshManifest() | ||
242 | 259 | | |||
243 | # Refreshes the manifest.yaml file for this archive | 260 | # Refreshes the manifest.yaml file for this archive | ||
244 | # This function should only be called on the archive server and will not function correctly on clients. | 261 | # This function should only be called on the archive server and will not function correctly on clients. | ||
245 | def refreshManifest( self ): | 262 | def refreshManifest( self ): | ||
▲ Show 20 Lines • Show All 42 Lines • Show Last 20 Lines |
Rather than writing this as a function, could we keep this as variable to reduce the number of changes below?
Also, the code appears to have indentation issues.