]> gitweb.fperrin.net Git - DictionaryPC.git/blob - googlecode_upload.py
Update to work with latest Dictionary repo version.
[DictionaryPC.git] / googlecode_upload.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2006, 2007 Google Inc. All Rights Reserved.
4 # Author: danderson@google.com (David Anderson)
5 #
6 # Script for uploading files to a Google Code project.
7 #
8 # This is intended to be both a useful script for people who want to
9 # streamline project uploads and a reference implementation for
10 # uploading files to Google Code projects.
11 #
12 # To upload a file to Google Code, you need to provide a path to the
13 # file on your local machine, a small summary of what the file is, a
14 # project name, and a valid account that is a member or owner of that
15 # project.  You can optionally provide a list of labels that apply to
16 # the file.  The file will be uploaded under the same name that it has
17 # in your local filesystem (that is, the "basename" or last path
18 # component).  Run the script with '--help' to get the exact syntax
19 # and available options.
20 #
21 # Note that the upload script requests that you enter your
22 # googlecode.com password.  This is NOT your Gmail account password!
23 # This is the password you use on googlecode.com for committing to
24 # Subversion and uploading files.  You can find your password by going
25 # to http://code.google.com/hosting/settings when logged in with your
26 # Gmail account. If you have already committed to your project's
27 # Subversion repository, the script will automatically retrieve your
28 # credentials from there (unless disabled, see the output of '--help'
29 # for details).
30 #
31 # If you are looking at this script as a reference for implementing
32 # your own Google Code file uploader, then you should take a look at
33 # the upload() function, which is the meat of the uploader.  You
34 # basically need to build a multipart/form-data POST request with the
35 # right fields and send it to https://PROJECT.googlecode.com/files .
36 # Authenticate the request using HTTP Basic authentication, as is
37 # shown below.
38 #
39 # Licensed under the terms of the Apache Software License 2.0:
40 #  http://www.apache.org/licenses/LICENSE-2.0
41 #
42 # Questions, comments, feature requests and patches are most welcome.
43 # Please direct all of these to the Google Code users group:
44 #  http://groups.google.com/group/google-code-hosting
45
46 """Google Code file uploader script.
47 """
48
49 __author__ = 'danderson@google.com (David Anderson)'
50
51 import httplib
52 import os.path
53 import optparse
54 import getpass
55 import base64
56 import sys
57
58
59 def upload(file, project_name, user_name, password, summary, labels=None):
60   """Upload a file to a Google Code project's file server.
61
62   Args:
63     file: The local path to the file.
64     project_name: The name of your project on Google Code.
65     user_name: Your Google account name.
66     password: The googlecode.com password for your account.
67               Note that this is NOT your global Google Account password!
68     summary: A small description for the file.
69     labels: an optional list of label strings with which to tag the file.
70
71   Returns: a tuple:
72     http_status: 201 if the upload succeeded, something else if an
73                  error occured.
74     http_reason: The human-readable string associated with http_status
75     file_url: If the upload succeeded, the URL of the file on Google
76               Code, None otherwise.
77   """
78   # The login is the user part of user@gmail.com. If the login provided
79   # is in the full user@domain form, strip it down.
80   if user_name.endswith('@gmail.com'):
81     user_name = user_name[:user_name.index('@gmail.com')]
82
83   form_fields = [('summary', summary)]
84   if labels is not None:
85     form_fields.extend([('label', l.strip()) for l in labels])
86
87   content_type, body = encode_upload_request(form_fields, file)
88
89   upload_host = '%s.googlecode.com' % project_name
90   upload_uri = '/files'
91   auth_token = base64.b64encode('%s:%s'% (user_name, password))
92   headers = {
93     'Authorization': 'Basic %s' % auth_token,
94     'User-Agent': 'Googlecode.com uploader v0.9.4',
95     'Content-Type': content_type,
96     }
97
98   server = httplib.HTTPSConnection(upload_host)
99   server.request('POST', upload_uri, body, headers)
100   resp = server.getresponse()
101   server.close()
102
103   if resp.status == 201:
104     location = resp.getheader('Location', None)
105   else:
106     location = None
107   return resp.status, resp.reason, location
108
109
110 def encode_upload_request(fields, file_path):
111   """Encode the given fields and file into a multipart form body.
112
113   fields is a sequence of (name, value) pairs. file is the path of
114   the file to upload. The file will be uploaded to Google Code with
115   the same file name.
116
117   Returns: (content_type, body) ready for httplib.HTTP instance
118   """
119   BOUNDARY = '----------Googlecode_boundary_reindeer_flotilla'
120   CRLF = '\r\n'
121
122   body = []
123
124   # Add the metadata about the upload first
125   for key, value in fields:
126     body.extend(
127       ['--' + BOUNDARY,
128        'Content-Disposition: form-data; name="%s"' % key,
129        '',
130        value,
131        ])
132
133   # Now add the file itself
134   file_name = os.path.basename(file_path)
135   f = open(file_path, 'rb')
136   file_content = f.read()
137   f.close()
138
139   body.extend(
140     ['--' + BOUNDARY,
141      'Content-Disposition: form-data; name="filename"; filename="%s"'
142      % file_name,
143      # The upload server determines the mime-type, no need to set it.
144      'Content-Type: application/octet-stream',
145      '',
146      file_content,
147      ])
148
149   # Finalize the form body
150   body.extend(['--' + BOUNDARY + '--', ''])
151
152   return 'multipart/form-data; boundary=%s' % BOUNDARY, CRLF.join(body)
153
154
155 def upload_find_auth(file_path, project_name, summary, labels=None,
156                      user_name=None, password=None, tries=3):
157   """Find credentials and upload a file to a Google Code project's file server.
158
159   file_path, project_name, summary, and labels are passed as-is to upload.
160
161   Args:
162     file_path: The local path to the file.
163     project_name: The name of your project on Google Code.
164     summary: A small description for the file.
165     labels: an optional list of label strings with which to tag the file.
166     config_dir: Path to Subversion configuration directory, 'none', or None.
167     user_name: Your Google account name.
168     tries: How many attempts to make.
169   """
170
171   while tries > 0:
172     if user_name is None:
173       # Read username if not specified or loaded from svn config, or on
174       # subsequent tries.
175       sys.stdout.write('Please enter your googlecode.com username: ')
176       sys.stdout.flush()
177       user_name = sys.stdin.readline().rstrip()
178     if password is None:
179       # Read password if not loaded from svn config, or on subsequent tries.
180       print 'Please enter your googlecode.com password.'
181       print '** Note that this is NOT your Gmail account password! **'
182       print 'It is the password you use to access Subversion repositories,'
183       print 'and can be found here: http://code.google.com/hosting/settings'
184       password = getpass.getpass()
185
186     status, reason, url = upload(file_path, project_name, user_name, password,
187                                  summary, labels)
188     # Returns 403 Forbidden instead of 401 Unauthorized for bad
189     # credentials as of 2007-07-17.
190     if status in [httplib.FORBIDDEN, httplib.UNAUTHORIZED]:
191       # Rest for another try.
192       user_name = password = None
193       tries = tries - 1
194     else:
195       # We're done.
196       break
197
198   return status, reason, url
199
200
201 def main():
202   parser = optparse.OptionParser(usage='googlecode-upload.py -s SUMMARY '
203                                  '-p PROJECT [options] FILE')
204   parser.add_option('-s', '--summary', dest='summary',
205                     help='Short description of the file')
206   parser.add_option('-p', '--project', dest='project',
207                     help='Google Code project name')
208   parser.add_option('-u', '--user', dest='user',
209                     help='Your Google Code username')
210   parser.add_option('-w', '--password', dest='password',
211                     help='Your Google Code password')
212   parser.add_option('-l', '--labels', dest='labels',
213                     help='An optional list of comma-separated labels to attach '
214                     'to the file')
215
216   options, args = parser.parse_args()
217
218   if not options.summary:
219     parser.error('File summary is missing.')
220   elif not options.project:
221     parser.error('Project name is missing.')
222   elif len(args) < 1:
223     parser.error('File to upload not provided.')
224   elif len(args) > 1:
225     parser.error('Only one file may be specified.')
226
227   file_path = args[0]
228
229   if options.labels:
230     labels = options.labels.split(',')
231   else:
232     labels = None
233
234   status, reason, url = upload_find_auth(file_path, options.project,
235                                          options.summary, labels,
236                                          options.user, options.password)
237   if url:
238     print 'The file was uploaded successfully.'
239     print 'URL: %s' % url
240     return 0
241   else:
242     print 'An error occurred. Your file was not uploaded.'
243     print 'Google Code upload server said: %s (%s)' % (reason, status)
244     return 1
245
246
247 if __name__ == '__main__':
248   sys.exit(main())