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.
7 from StringIO
import StringIO
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
18 from zipfile
import ZipFile
, BadZipfile
25 def _MakeBlobstoreKey(version
):
26 return ZIP_KEY
+ '.' + str(version
)
29 def _GetAsyncFetchCallback(fetcher
,
35 fetch
= fetcher
.FetchAsync(ZIP_KEY
, username
=username
, password
=password
)
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
47 except urlfetch
.DownloadError
as e
:
48 logging
.error('Bad github zip file: %s' % e
)
50 if key_to_delete
is not None:
51 blobstore
.Delete(_MakeBlobstoreKey(key_to_delete
, BLOBSTORE_GITHUB
))
53 return_zip
= ZipFile(StringIO(blob
))
54 except BadZipfile
as e
:
55 logging
.error('Bad github zip file: %s' % e
)
58 blobstore
.Set(_MakeBlobstoreKey(key_to_set
), blob
, BLOBSTORE_GITHUB
)
64 class GithubFileSystem(FileSystem
):
66 def CreateChromeAppsSamples(object_store_creator
):
67 return GithubFileSystem(
68 '%s/GoogleChrome/chrome-app-samples' % url_constants
.GITHUB_REPOS
,
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(
82 password_data
= password_store
.GetMulti(('username', 'password')).Get()
83 self
._username
, self
._password
= (password_data
.get('username'),
84 password_data
.get('password'))
86 password_store
.SetMulti({'username': USERNAME
, 'password': PASSWORD
})
87 self
._username
, self
._password
= (USERNAME
, PASSWORD
)
90 self
._fetcher
= AppEngineUrlFetcher(url
)
91 self
._blobstore
= blobstore
92 self
._stat
_object
_store
= object_store_creator
.Create(GithubFileSystem
)
94 self
._GetZip
(self
.Stat(ZIP_KEY
).version
)
96 def _GetZip(self
, version
):
98 blob
= self
._blobstore
.Get(_MakeBlobstoreKey(version
), BLOBSTORE_GITHUB
)
99 except blobstore
.BlobNotFoundError
:
100 self
._zip
_file
= Future(value
=None)
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)
110 self
._zip
_file
= Future(
111 callback
=_GetAsyncFetchCallback(self
._fetcher
,
116 key_to_delete
=self
._version
))
117 self
._version
= version
119 def _ReadFile(self
, path
):
121 zip_file
= self
._zip
_file
.Get()
122 except Exception as e
:
123 logging
.error('Github ReadFile error: %s' % e
)
126 logging
.error('Bad github zip file.')
128 prefix
= zip_file
.namelist()[0]
129 return zip_file
.read(prefix
+ path
)
131 def _ListDir(self
, path
):
133 zip_file
= self
._zip
_file
.Get()
134 except Exception as e
:
135 logging
.error('Github ListDir error: %s' % e
)
138 logging
.error('Bad github zip file.')
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
)
155 if IsDirectory(path
):
156 result
[path
] = self
._ListDir
(path
)
158 result
[path
] = self
._ReadFile
(path
)
159 return Future(value
=result
)
161 def _DefaultStat(self
, path
):
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
)
175 result
= self
._fetcher
.Fetch('commits/HEAD',
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
)
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.
194 version
= json
.loads(result
.content
)['sha']
195 self
._stat
_object
_store
.Set(path
, version
)
196 return StatInfo(version
)
197 except StandardError as e
:
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
))