Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / common / extensions / docs / server2 / github_file_system.py
blobeaa08ab89a34b3a55c9c8d9ce6d14d585b111276
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 import json
6 import logging
7 from StringIO import StringIO
8 import posixpath
10 from appengine_blobstore import AppEngineBlobstore, BLOBSTORE_GITHUB
11 from appengine_url_fetcher import AppEngineUrlFetcher
12 from appengine_wrappers import urlfetch, blobstore
13 from docs_server_utils import StringIdentity
14 from file_system import FileSystem, StatInfo
15 from future import Future
16 from path_util import IsDirectory
17 import url_constants
18 from zipfile import ZipFile, BadZipfile
20 ZIP_KEY = 'zipball'
21 USERNAME = None
22 PASSWORD = None
25 def _MakeBlobstoreKey(version):
26 return ZIP_KEY + '.' + str(version)
29 def _GetAsyncFetchCallback(fetcher,
30 username,
31 password,
32 blobstore,
33 key_to_set,
34 key_to_delete=None):
35 fetch = fetcher.FetchAsync(ZIP_KEY, username=username, password=password)
37 def resolve():
38 try:
39 result = fetch.Get()
40 # Check if Github authentication failed.
41 if result.status_code == 401:
42 logging.error('Github authentication failed for %s, falling back to '
43 'unauthenticated.' % USERNAME)
44 blob = fetcher.Fetch(ZIP_KEY).content
45 else:
46 blob = result.content
47 except urlfetch.DownloadError as e:
48 logging.error('Bad github zip file: %s' % e)
49 return None
50 if key_to_delete is not None:
51 blobstore.Delete(_MakeBlobstoreKey(key_to_delete, BLOBSTORE_GITHUB))
52 try:
53 return_zip = ZipFile(StringIO(blob))
54 except BadZipfile as e:
55 logging.error('Bad github zip file: %s' % e)
56 return None
58 blobstore.Set(_MakeBlobstoreKey(key_to_set), blob, BLOBSTORE_GITHUB)
59 return return_zip
61 return resolve
64 class GithubFileSystem(FileSystem):
65 @staticmethod
66 def CreateChromeAppsSamples(object_store_creator):
67 return GithubFileSystem(
68 '%s/GoogleChrome/chrome-app-samples' % url_constants.GITHUB_REPOS,
69 AppEngineBlobstore(),
70 object_store_creator)
72 def __init__(self, url, blobstore, object_store_creator):
73 # If we key the password store on the app version then the whole advantage
74 # of having it in the first place is greatly lessened (likewise it should
75 # always start populated).
76 password_store = object_store_creator.Create(
77 GithubFileSystem,
78 app_version=None,
79 category='password',
80 start_empty=False)
81 if USERNAME is None:
82 password_data = password_store.GetMulti(('username', 'password')).Get()
83 self._username, self._password = (password_data.get('username'),
84 password_data.get('password'))
85 else:
86 password_store.SetMulti({'username': USERNAME, 'password': PASSWORD})
87 self._username, self._password = (USERNAME, PASSWORD)
89 self._url = url
90 self._fetcher = AppEngineUrlFetcher(url)
91 self._blobstore = blobstore
92 self._stat_object_store = object_store_creator.Create(GithubFileSystem)
93 self._version = None
94 self._GetZip(self.Stat(ZIP_KEY).version)
96 def _GetZip(self, version):
97 try:
98 blob = self._blobstore.Get(_MakeBlobstoreKey(version), BLOBSTORE_GITHUB)
99 except blobstore.BlobNotFoundError:
100 self._zip_file = Future(value=None)
101 return
102 if blob is not None:
103 try:
104 self._zip_file = Future(value=ZipFile(StringIO(blob)))
105 except BadZipfile as e:
106 self._blobstore.Delete(_MakeBlobstoreKey(version), BLOBSTORE_GITHUB)
107 logging.error('Bad github zip file: %s' % e)
108 self._zip_file = Future(value=None)
109 else:
110 self._zip_file = Future(
111 callback=_GetAsyncFetchCallback(self._fetcher,
112 self._username,
113 self._password,
114 self._blobstore,
115 version,
116 key_to_delete=self._version))
117 self._version = version
119 def _ReadFile(self, path):
120 try:
121 zip_file = self._zip_file.Get()
122 except Exception as e:
123 logging.error('Github ReadFile error: %s' % e)
124 return ''
125 if zip_file is None:
126 logging.error('Bad github zip file.')
127 return ''
128 prefix = zip_file.namelist()[0]
129 return zip_file.read(prefix + path)
131 def _ListDir(self, path):
132 try:
133 zip_file = self._zip_file.Get()
134 except Exception as e:
135 logging.error('Github ListDir error: %s' % e)
136 return []
137 if zip_file is None:
138 logging.error('Bad github zip file.')
139 return []
140 filenames = zip_file.namelist()
141 # Take out parent directory name (GoogleChrome-chrome-app-samples-c78a30f)
142 filenames = [f[len(filenames[0]):] for f in filenames]
143 # Remove the path of the directory we're listing from the filenames.
144 filenames = [f[len(path):] for f in filenames
145 if f != path and f.startswith(path)]
146 # Remove all files not directly in this directory.
147 return [f for f in filenames if f[:-1].count('/') == 0]
149 def Read(self, paths, skip_not_found=False):
150 version = self.Stat(ZIP_KEY).version
151 if version != self._version:
152 self._GetZip(version)
153 result = {}
154 for path in paths:
155 if IsDirectory(path):
156 result[path] = self._ListDir(path)
157 else:
158 result[path] = self._ReadFile(path)
159 return Future(value=result)
161 def _DefaultStat(self, path):
162 version = 0
163 # TODO(kalman): we should replace all of this by wrapping the
164 # GithubFileSystem in a CachingFileSystem. A lot of work has been put into
165 # CFS to be robust, and GFS is missing out.
166 # For example: the following line is wrong, but it could be moot.
167 self._stat_object_store.Set(path, version)
168 return StatInfo(version)
170 def Stat(self, path):
171 version = self._stat_object_store.Get(path).Get()
172 if version is not None:
173 return StatInfo(version)
174 try:
175 result = self._fetcher.Fetch('commits/HEAD',
176 username=USERNAME,
177 password=PASSWORD)
178 except urlfetch.DownloadError as e:
179 logging.warning('GithubFileSystem Stat: %s' % e)
180 return self._DefaultStat(path)
182 # Check if Github authentication failed.
183 if result.status_code == 401:
184 logging.warning('Github authentication failed for %s, falling back to '
185 'unauthenticated.' % USERNAME)
186 try:
187 result = self._fetcher.Fetch('commits/HEAD')
188 except urlfetch.DownloadError as e:
189 logging.warning('GithubFileSystem Stat: %s' % e)
190 return self._DefaultStat(path)
192 # Parse response JSON - but sometimes github gives us invalid JSON.
193 try:
194 version = json.loads(result.content)['sha']
195 self._stat_object_store.Set(path, version)
196 return StatInfo(version)
197 except StandardError as e:
198 logging.warning(
199 ('%s: got invalid or unexpected JSON from github. Response status ' +
200 'was %s, content %s') % (e, result.status_code, result.content))
201 return self._DefaultStat(path)
203 def GetIdentity(self):
204 return '%s@%s' % (self.__class__.__name__, StringIdentity(self._url))