text: #!/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
  
  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)
  
  
  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 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 'LOCAL_CACHE_NO_URL_AVAILABLE'
  
    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 'LOCAL_CACHE_NO_URL_AVAILABLE'
      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)
  