| # Copyright 2014 Google Inc. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| """File based cache for the discovery document. |
| |
| The cache is stored in a single file so that multiple processes can |
| share the same cache. It locks the file whenever accessing to the |
| file. When the cache content is corrupted, it will be initialized with |
| an empty cache. |
| """ |
| |
| from __future__ import division |
| |
| import datetime |
| import json |
| import logging |
| import os |
| import tempfile |
| |
| try: |
| from oauth2client.contrib.locked_file import LockedFile |
| except ImportError: |
| # oauth2client < 2.0.0 |
| try: |
| from oauth2client.locked_file import LockedFile |
| except ImportError: |
| # oauth2client > 4.0.0 or google-auth |
| raise ImportError( |
| "file_cache is unavailable when using oauth2client >= 4.0.0 or google-auth" |
| ) |
| |
| from . import base |
| from ..discovery_cache import DISCOVERY_DOC_MAX_AGE |
| |
| LOGGER = logging.getLogger(__name__) |
| |
| FILENAME = "google-api-python-client-discovery-doc.cache" |
| EPOCH = datetime.datetime(1970, 1, 1) |
| |
| |
| def _to_timestamp(date): |
| try: |
| return (date - EPOCH).total_seconds() |
| except AttributeError: |
| # The following is the equivalent of total_seconds() in Python2.6. |
| # See also: https://docs.python.org/2/library/datetime.html |
| delta = date - EPOCH |
| return ( |
| delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6 |
| ) / 10**6 |
| |
| |
| def _read_or_initialize_cache(f): |
| f.file_handle().seek(0) |
| try: |
| cache = json.load(f.file_handle()) |
| except Exception: |
| # This means it opens the file for the first time, or the cache is |
| # corrupted, so initializing the file with an empty dict. |
| cache = {} |
| f.file_handle().truncate(0) |
| f.file_handle().seek(0) |
| json.dump(cache, f.file_handle()) |
| return cache |
| |
| |
| class Cache(base.Cache): |
| """A file based cache for the discovery documents.""" |
| |
| def __init__(self, max_age): |
| """Constructor. |
| |
| Args: |
| max_age: Cache expiration in seconds. |
| """ |
| self._max_age = max_age |
| self._file = os.path.join(tempfile.gettempdir(), FILENAME) |
| f = LockedFile(self._file, "a+", "r") |
| try: |
| f.open_and_lock() |
| if f.is_locked(): |
| _read_or_initialize_cache(f) |
| # If we can not obtain the lock, other process or thread must |
| # have initialized the file. |
| except Exception as e: |
| LOGGER.warning(e, exc_info=True) |
| finally: |
| f.unlock_and_close() |
| |
| def get(self, url): |
| f = LockedFile(self._file, "r+", "r") |
| try: |
| f.open_and_lock() |
| if f.is_locked(): |
| cache = _read_or_initialize_cache(f) |
| if url in cache: |
| content, t = cache.get(url, (None, 0)) |
| if _to_timestamp(datetime.datetime.now()) < t + self._max_age: |
| return content |
| return None |
| else: |
| LOGGER.debug("Could not obtain a lock for the cache file.") |
| return None |
| except Exception as e: |
| LOGGER.warning(e, exc_info=True) |
| finally: |
| f.unlock_and_close() |
| |
| def set(self, url, content): |
| f = LockedFile(self._file, "r+", "r") |
| try: |
| f.open_and_lock() |
| if f.is_locked(): |
| cache = _read_or_initialize_cache(f) |
| cache[url] = (content, _to_timestamp(datetime.datetime.now())) |
| # Remove stale cache. |
| for k, (_, timestamp) in list(cache.items()): |
| if ( |
| _to_timestamp(datetime.datetime.now()) |
| >= timestamp + self._max_age |
| ): |
| del cache[k] |
| f.file_handle().truncate(0) |
| f.file_handle().seek(0) |
| json.dump(cache, f.file_handle()) |
| else: |
| LOGGER.debug("Could not obtain a lock for the cache file.") |
| except Exception as e: |
| LOGGER.warning(e, exc_info=True) |
| finally: |
| f.unlock_and_close() |
| |
| |
| cache = Cache(max_age=DISCOVERY_DOC_MAX_AGE) |