# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""
Fetch and cache artifacts from URLs.
This module manages fetching artifacts from URLS and purging old
artifacts using a simple Least Recently Used cache.
This module requires certain modules be importable from the ambient Python
environment. Consumers will need to arrange this themselves.
The bulk of the complexity isin managing and persisting several caches. If
we found a Python LRU cache that pickled cleanly, we could remove a lot of
this code! Sadly, I found no such candidate implementations, so we pickle
pylru caches manually.
None of the instances (or the underlying caches) are safe for concurrent use.
A future need, perhaps. """
import binascii import hashlib import logging import os
import dlmanager import mozpack.path as mozpath import six import six.moves.urllib.parse as urlparse
from mozbuild.dirutils import mkdir
# Using 'DownloadManager' through the provided interface we # can't directly specify a 'chunk_size' for the 'Download' it manages. # One way to get it to use the 'chunk_size' we want is to monkeypatch # the defaults of the init function for the 'Download' class.
CHUNK_SIZE = 16 * 1024 * 1024 # 16 MB in bytes.
dl_init = dlmanager.Download.__init__
dl_init.__defaults__ = (
dl_init.__defaults__[:1] + (CHUNK_SIZE,) + dl_init.__defaults__[2:]
)
# Minimum number of downloaded artifacts to keep. Each artifact can be very large, # so don't make this to large!
MIN_CACHED_ARTIFACTS = 12
# Maximum size of the downloaded artifacts to keep in cache, in bytes (2GiB).
MAX_CACHED_ARTIFACTS_SIZE = 2 * 1024 * 1024 * 1024
class ArtifactPersistLimit(dlmanager.PersistLimit): """Handle persistence for a cache of artifacts.
When instantiating a DownloadManager, it starts by filling the
PersistLimit instance it's given with register_dir_content. In practice, this registers all the files already in the cache directory.
After a download finishes, the newly downloaded file is registered, and the
oldest files registered to the PersistLimit instance are removed depending
on the size and file limits it's configured for.
This is all good, but there are a few tweaks we want here:
- We have pickle files in the cache directory that we don't want purged.
- Files that were just downloaded in the same session shouldn't be
purged. (iffor some reason we end up downloading more than the default
max size, we don't want the files to be purged)
To achieve this, this subclass of PersistLimit inhibits the register_file
method for pickle files and tracks what files were downloaded in the same
session to avoid removing them.
The register_file method may be used to register cache matches too, so that
later sessions know they were freshly used. """
def log(self, *args, **kwargs): if self._log:
self._log(*args, **kwargs)
def register_file(self, path): if (
path.endswith(".pickle") or path.endswith(".checksum") or os.path.basename(path) == ".metadata_never_index"
): return ifnot self._registering_dir: # Touch the file so that subsequent calls to a mach artifact # command know it was recently used. While remove_old_files # is based on access time, in various cases, the access time is not # updated when just reading the file, so we force an update. try:
os.utime(path, None) except OSError: pass
self._downloaded_now.add(path)
super(ArtifactPersistLimit, self).register_file(path)
def log(self, *args, **kwargs): if self._log:
self._log(*args, **kwargs)
def fetch(self, url, force=False):
fname = os.path.basename(url) try: # Use the file name from the url if it looks like a hash digest. if len(fname) notin (32, 40, 56, 64, 96, 128): raise TypeError()
binascii.unhexlify(fname) except (TypeError, binascii.Error): # We download to a temporary name like HASH[:16]-basename to # differentiate among URLs with the same basenames. We used to then # extract the build ID from the downloaded artifact and use it to make a # human readable unique name, but extracting build IDs is time consuming # (especially on Mac OS X, where we must mount a large DMG file).
hash = hashlib.sha256(six.ensure_binary(url)).hexdigest()[:16] # Strip query string and fragments.
basename = os.path.basename(urlparse.urlparse(url).path)
fname = hash + "-" + basename
if dl:
self.log(
logging.INFO, "artifact",
{"path": path}, "Downloading artifact to local cache: {path}",
)
dl.set_progress(download_progress)
dl.wait() else:
self.log(
logging.INFO, "artifact",
{"path": path}, "Using artifact from local cache: {path}",
) # Avoid the file being removed if it was in the cache already.
path = os.path.join(self._cache_dir, fname)
self._persist_limit.register_file(path)
return os.path.abspath(mozpath.join(self._cache_dir, fname)) finally: # Cancel any background downloads in progress.
self._download_manager.cancel()
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.