/
sites_uploader.py
230 lines (189 loc) · 7.35 KB
/
sites_uploader.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Upload an attachment to a sites page."""
import getpass
import optparse
import os
import pickle
import sys
import gdata.data
import gdata.gauth
import gdata.sites.client
import gdata.sites.data
import oneshot
VERSION = '1.0'
SOURCE = 'Dom-SitesUploader-%s' % VERSION
# OAuth bits. We use “anonymous” to behave as an unregistered application.
# http://code.google.com/apis/accounts/docs/OAuth_ref.html#SigningOAuth
CONSUMER_KEY = 'anonymous'
CONSUMER_SECRET = 'anonymous'
# TODO: limit scope to just what we actually need.
SCOPES = ['http://sites.google.com/feeds/',
'https://sites.google.com/feeds/']
class Error(Exception):
pass
class ClientAuthorizer():
"""Add authorization to a client."""
def __init__(self, consumer_key=CONSUMER_KEY,
consumer_secret=CONSUMER_SECRET, scopes=None):
"""Construct a new ClientAuthorizer."""
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
if scopes:
self.scopes = scopes
else:
self.scopes = SCOPES
self.tokfile = os.path.expanduser('~/.%s.tok' % os.path.basename(sys.argv[0]))
def ReadToken(self):
"""Read in the stored auth token object.
Returns:
The stored token object, or None.
"""
if os.path.exists(self.tokfile):
fh = open(self.tokfile, 'rb')
tok = pickle.load(fh)
fh.close()
return tok
else:
return None
def WriteToken(self, tok):
"""Write the token object to a file."""
fh = open(self.tokfile, 'wb')
os.chmod(self.tokfile, 0600)
pickle.dump(tok, fh)
fh.close()
def FetchClientToken(self, client):
"""Ensure client.auth_token is valid.
If a stored token is available, it will be used. Otherwise, this goes
through the OAuth rituals described at:
http://code.google.com/apis/gdata/docs/auth/oauth.html#Examples
"""
access_token = self.ReadToken()
if not access_token:
httpd = oneshot.ParamsReceiverServer()
# TODO Find a way to pass "xoauth_displayname" parameter.
request_token = client.GetOAuthToken(
self.scopes, httpd.my_url(), self.consumer_key, self.consumer_secret)
url = request_token.generate_authorization_url(google_apps_domain=client.domain)
print 'Please visit this URL to continue authorization:'
print url
httpd.serve_until_result()
request_token = gdata.gauth.AuthorizeRequestToken(request_token, httpd.result)
access_token = client.GetAccessToken(request_token)
self.WriteToken(access_token)
client.auth_token = access_token
class SitesUploader(object):
"""A utility to upload a file to a sites page."""
def __init__(self, domain, site, ssl=True, debug=False):
"""Construct a new SitesUploader.
Args:
domain: The google apps domain to upload to.
site: The site within the domain.
ssl: Boolean. Should SSL be used for all connections? Default: True.
debug: Boolean. Should debug output be produced. Default: False
"""
self.domain = domain
self.site = site
self.ssl = ssl
self.debug = debug
self.client = None
def _MakeClient(self, client_authz=None):
"""Return a populated SitesClient object."""
client = gdata.sites.client.SitesClient(source=SOURCE, site=self.site,
domain=self.domain)
client.ssl = self.ssl
client.http_client.debug = self.debug
# Make sure we've got a valid token in the client.
if not client_authz:
client_authz = ClientAuthorizer()
client_authz.FetchClientToken(client)
return client
@property
def _client(self):
if not self.client:
self.client = self._MakeClient()
return self.client
def _GetPage(self, client, page):
"""Return the ContentEntry for page.
Throws:
Error: if the page can't be found.
"""
uri = '%s?path=%s' % (client.MakeContentFeedUri(), page)
feed = client.GetContentFeed(uri)
if not feed.entry:
raise Error("can't find page %s" % opts.page)
return feed.entry[0]
def _FindAttachment(self, client, page, media_source):
"""Return the attachment for media_source, or None."""
# The id of the parent we need to query by isn't exposed directly, so we
# parse it out of the id. I'm not sure that this is the best way…
uri = '%s?parent=%s&kind=attachment' % (client.MakeContentFeedUri(),
os.path.basename(page.id.text))
feed = client.GetContentFeed(uri)
for entry in feed.entry:
href = entry.GetAlternateLink().href
# I'm not 100% happy with this check, but it appears to work.
if os.path.basename(href) == media_source.file_name:
return entry
return None
def UploadFile(self, page, file_to_upload,
content_type='application/octet-stream'):
"""Upload file to page.
If file is already attached to page, it will be replaced.
Args:
page: The site-relative path to the page you wish to upload to.
file_to_upload: A pathname on the local filesystem.
content_type: The MIME type of the file to be uploaded.
Defaults to "application/octet-stream".
Returns:
A ContentEntry object for the attachment. The URL for the newly
uploaded attachment is in GetAlternateLink().href.
"""
client = self._client
parent = self._GetPage(client, page)
ms = gdata.data.MediaSource(file_path=file_to_upload,
content_type=content_type)
attachment = self._FindAttachment(client, parent, ms)
if attachment:
client.Update(attachment, media_source=ms)
else:
attachment = client.UploadAttachment(ms, parent)
return attachment
def GetParser():
"""Return a populated OptionParser"""
usage = u"""usage: %prog [options] /path/to/file
Upload “file” to a page on the specified jotspot site.
"""
parser = optparse.OptionParser(usage=usage, version=VERSION)
parser.add_option('--domain', dest='domain', help='* Hosted domain')
parser.add_option('--site', dest='site', help='* Site within domain')
parser.add_option('--ssl', dest='ssl', action='store_true', default=False,
help='Use https for communications (%default)')
parser.add_option('--debug', dest='debug', action='store_true', default=False,
help='Enable debug output (HTTP conversation)')
parser.add_option('--content_type', dest='content_type',
default='application/octet-stream',
help='Content-type of file to be uploaded (%default)')
parser.add_option('--page', dest='page', help='* Page to upload to')
return parser
def main():
parser = GetParser()
(opts, args) = parser.parse_args()
if len(args) == 0:
parser.error('must specify file(s) to upload')
if not opts.domain:
parser.error('please specify --domain')
if not opts.site:
parser.error('please specify --site')
if not opts.page:
parser.error("please specify --page")
if not opts.page.startswith('/'):
opts.page = '/' + opts.page
uploader = SitesUploader(opts.domain, opts.site, opts.ssl, opts.debug)
for file_to_upload in args:
if not os.path.exists(file_to_upload):
raise Error("no such file: %s" % file_to_upload)
attachment = uploader.UploadFile(opts.page, file_to_upload, opts.content_type)
print attachment.GetAlternateLink().href
if __name__ == '__main__':
main()