blob: bbfdf626f026fc78c84525bb01ee548e0da35d87 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012 The Native Client Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Cache accesses to GSDStorage locally.
Operations are provided to read/write whole files and to
read/write strings.
Read from GSDStorage if nothing exists locally.
"""
import os
import re
from pynacl import file_tools
KEY_PATTERN = re.compile('^[A-Za-z0-9_/.]+$')
def ValidateKey(key):
if KEY_PATTERN.match(key) is None:
raise KeyError('Invalid storage key "%s"' % key)
def LocalFileURL(local_file):
abs_path = os.path.abspath(local_file)
if not abs_path.startswith('/'):
# Windows paths needs an extra slash for the file protocol.
return 'file:///' + abs_path
else:
return 'file://' + abs_path
class LocalStorageCache(object):
"""A caching wrapper for reading a GSDStorage object or storing locally.
Allow reading/writing to key, value pairs in local files.
Reads fall back to remote storage.
Restricts keys to a limited regex.
Is not atomic in the face of concurrent writers / readers on Windows.
"""
def __init__(self, cache_path, storage):
"""Init for this class.
Args:
cache_path: Path to a database to store a local cache in.
storage: A GSDStorage style object to fallback to for reads.
"""
self._cache_path = os.path.abspath(cache_path)
file_tools.MakeDirectoryIfAbsent(self._cache_path)
self._storage = storage
def Exists(self, key):
"""Queries whether or not a key exists.
Args:
key: Key file is stored under.
Returns:
URL of existing key, or False if file does not exist.
"""
ValidateKey(key)
cache_file = os.path.join(self._cache_path, key)
if os.path.exists(cache_file):
return LocalFileURL(cache_file)
return False
def PutFile(self, path, key):
"""Write a file to storage.
Args:
path: Path of the file to write.
key: Key to store file under.
Returns:
URL written to.
"""
return self.PutData(file_tools.ReadFile(path), key)
def PutData(self, data, key):
"""Write data to storage.
Args:
data: Data to store.
key: Key to store file under.
Returns:
URL written to.
"""
ValidateKey(key)
cache_file = os.path.join(self._cache_path, key)
cache_dir = os.path.dirname(cache_file)
if not os.path.exists(cache_dir):
os.makedirs(cache_dir)
file_tools.AtomicWriteFile(data, cache_file)
return LocalFileURL(cache_file)
def GetFile(self, key, path):
"""Read a file from storage.
Args:
key: Key to store file under.
path: Destination filename.
Returns:
URL used on success or None for failure.
"""
ValidateKey(key)
cache_file = os.path.join(self._cache_path, key)
if os.path.exists(cache_file):
data = file_tools.ReadFile(cache_file)
file_tools.WriteFile(data, path)
return LocalFileURL(cache_file)
else:
return self._storage.GetFile(key, path)
def GetData(self, key):
"""Read data from global storage.
Args:
key: Key to store file under.
Returns:
Data from storage, or None for failure.
"""
ValidateKey(key)
cache_file = os.path.join(self._cache_path, key)
if os.path.exists(cache_file):
return file_tools.ReadFile(cache_file)
else:
return self._storage.GetData(key)