147 lines
4.7 KiB
Python
147 lines
4.7 KiB
Python
|
# 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 accesing 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
|
||
|
import threading
|
||
|
|
||
|
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.utcfromtimestamp(0)
|
||
|
|
||
|
|
||
|
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)
|