1a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# Copyright 2013 The Chromium Authors. All rights reserved.
2a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be
3a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)# found in the LICENSE file.
4a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
5a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from datetime import datetime, timedelta
6a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)from file_system import FileNotFoundError
7a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from future import Future
8a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)from patcher import Patcher
9a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
10a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)_VERSION_CACHE_MAXAGE = timedelta(seconds=5)
11a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
12a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)''' Append @version for keys to distinguish between different patchsets of
13a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)an issue.
14a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)'''
15a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)def _MakeKey(path, version):
16a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  return '%s@%s' % (path, version)
17a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
18a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)def _ToObjectStoreValue(raw_value, version):
19a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  return dict((_MakeKey(key, version), raw_value[key])
20a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)              for key in raw_value)
21a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
22a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)def _FromObjectStoreValue(raw_value):
23a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  return dict((key[0:key.rfind('@')], raw_value[key]) for key in raw_value)
24a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
25a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)class _AsyncUncachedFuture(object):
26a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def __init__(self,
27a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               version,
28a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               paths,
29a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               cached_value,
30a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               missing_paths,
31a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               fetch_delegate,
32a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               object_store):
33a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._version = version
34a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._paths = paths
35a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._cached_value = cached_value
36a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._missing_paths = missing_paths
37a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._fetch_delegate = fetch_delegate
38a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._object_store = object_store
39a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
40a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def Get(self):
41a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    uncached_value = self._fetch_delegate.Get()
42a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)    self._object_store.SetMulti(_ToObjectStoreValue(uncached_value,
43a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                                    self._version))
44a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
45a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    for path in self._missing_paths:
46a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      if uncached_value.get(path) is None:
47a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        raise FileNotFoundError('File %s was not found in the patch.' % path)
48a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)      self._cached_value[path] = uncached_value[path]
49a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
50a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return self._cached_value
51a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
52a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)class CachingRietveldPatcher(Patcher):
53a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  ''' CachingRietveldPatcher implements a caching layer on top of |patcher|.
54a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  In theory, it can be used with any class that implements Patcher. But this
55a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  class assumes that applying to all patched files at once is more efficient
56a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  than applying to individual files.
57a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  '''
58a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def __init__(self,
59a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               rietveld_patcher,
60a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               object_store_creator,
61a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)               test_datetime=datetime):
62a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._patcher = rietveld_patcher
63ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    def create_object_store(category):
64ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch      return object_store_creator.Create(
65ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          CachingRietveldPatcher,
66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch          category='%s/%s' % (rietveld_patcher.GetIdentity(), category))
67ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._version_object_store = create_object_store('version')
68ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._list_object_store = create_object_store('list')
69ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    self._file_object_store = create_object_store('file')
70a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._datetime = test_datetime
71a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
72a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def GetVersion(self):
73a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    key = 'version'
74a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    value = self._version_object_store.Get(key).Get()
75a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if value is not None:
76a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      version, time = value
77a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      if self._datetime.now() - time < _VERSION_CACHE_MAXAGE:
78a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)        return version
79a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
80a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    version = self._patcher.GetVersion()
81a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._version_object_store.Set(key,
82a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                (version, self._datetime.now()))
83a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return version
84a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
85a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)  def GetPatchedFiles(self, version=None):
86a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if version is None:
87a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      version = self.GetVersion()
88a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    patched_files = self._list_object_store.Get(version).Get()
89a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if patched_files is not None:
90a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return patched_files
91a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
92a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    patched_files = self._patcher.GetPatchedFiles(version)
93a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    self._list_object_store.Set(version, patched_files)
94a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return patched_files
95a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
96a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)  def Apply(self, paths, file_system, version=None):
97a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if version is None:
98a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      version = self.GetVersion()
99a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    added, deleted, modified = self.GetPatchedFiles(version)
100a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    cached_value = _FromObjectStoreValue(self._file_object_store.
101a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7Torne (Richard Coles)        GetMulti([_MakeKey(path, version) for path in paths]).Get())
102a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    missing_paths = list(set(paths) - set(cached_value.keys()))
103a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    if len(missing_paths) == 0:
104a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)      return Future(value=cached_value)
105a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)
106a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)    return _AsyncUncachedFuture(version,
107a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                paths,
108a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                cached_value,
109a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                missing_paths,
110a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                self._patcher.Apply(set(added) | set(modified),
111a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                                    None,
112a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                                    version),
113a93a17c8d99d686bd4a1511e5504e5e6cc9fcadfTorne (Richard Coles)                                self._file_object_store)
114ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch
115ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  def GetIdentity(self):
116ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    return self._patcher.GetIdentity()
117