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