source: subversion/applications/utils/import/bulkupload/upload-python2.py @ 34714

Last change on this file since 34714 was 19989, checked in by balrog-kun, 10 years ago

Set the executable bits on the executables. (this directory still needs a clean-up)

  • Property svn:executable set to *
File size: 12.1 KB
Line 
1#! /usr/bin/python2
2# vim: fileencoding=utf-8 encoding=utf-8 et sw=4
3
4# Copyright (C) 2009 Jacek Konieczny <jajcus@jajcus.net>
5# Copyright (C) 2009 Andrzej Zaborowski <balrogg@gmail.com>
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20
21"""
22Uploads complete osmChange 0.3 files.  Use your login (not email) as username.
23"""
24
25__version__ = "$Revision: 21 $"
26
27import os
28import subprocess
29import sys
30import traceback
31
32import httplib
33
34import xml.etree.cElementTree as ElementTree
35import urlparse
36
37import locale, codecs
38try:
39    locale.setlocale(locale.LC_ALL, "en_US.UTF-8")
40    encoding = locale.getlocale()[1]
41    sys.stdout = codecs.getwriter(encoding)(sys.stdout, errors = "replace")
42    sys.stderr = codecs.getwriter(encoding)(sys.stderr, errors = "replace")
43except locale.Error:
44    pass
45
46class HTTPError(Exception):
47    pass
48
49class OSM_API(object):
50    url = 'http://api.openstreetmap.org/'
51    def __init__(self, username = None, password = None):
52        if username and password:
53            self.username = username
54            self.password = password
55        else:
56            self.username = ""
57            self.password = ""
58        self.changeset = None
59        self.progress_msg = None
60
61    def __del__(self):
62        #if self.changeset is not None:
63        #    self.close_changeset()
64        pass
65
66    def msg(self, mesg):
67        sys.stderr.write(u"\r%s…                        " % (self.progress_msg))
68        sys.stderr.write(u"\r%s%s" % (self.progress_msg, mesg))
69        sys.stderr.flush()
70
71    def request(self, conn, method, url, body, headers, progress):
72        if progress:
73            self.msg(u"making request")
74            conn.putrequest(method, url)
75            self.msg(u"sending headers")
76            if body:
77                conn.putheader('Content-Length', str(len(body)))
78            for hdr, value in headers.iteritems():
79                conn.putheader(hdr, value)
80            self.msg(u"end of headers")
81            conn.endheaders()
82            self.msg(u" 0%")
83            if body:
84                start = 0
85                size = len(body)
86                chunk = size / 100
87                if chunk < 16384:
88                    chunk = 16384
89                while start < size:
90                    end = min(size, start + chunk)
91                    conn.send(body[start:end])
92                    start = end
93                    self.msg(u"%2i%%" % (start * 100 / size))
94        else:
95            self.msg(u" ")
96            conn.request(method, url, body, headers)
97
98    def _run_request(self, method, url, body = None, progress = 0, content_type = "text/xml"):
99        url = urlparse.urljoin(self.url, url)
100        purl = urlparse.urlparse(url)
101        if purl.scheme != "http":
102            raise ValueError, "Unsupported url scheme: %r" % (purl.scheme,)
103        if ":" in purl.netloc:
104            host, port = purl.netloc.split(":", 1)
105            port = int(port)
106        else:
107            host = purl.netloc
108            port = None
109        url = purl.path
110        if purl.query:
111            url += "?" + query
112        headers = {}
113        if body:
114            headers["Content-Type"] = content_type
115
116        try_no_auth = 0
117
118        if not try_no_auth and not self.username:
119            raise HTTPError, (0, "Need a username")
120
121        try:
122            self.msg(u"connecting")
123            conn = httplib.HTTPConnection(host, port)
124#            conn.set_debuglevel(10)
125
126            if try_no_auth:
127                self.request(conn, method, url, body, headers, progress)
128                self.msg(u"waiting for status")
129                response = conn.getresponse()
130
131            if not try_no_auth or (response.status == httplib.UNAUTHORIZED and
132                    self.username):
133                if try_no_auth:
134                    conn.close()
135                    self.msg(u"re-connecting")
136                    conn = httplib.HTTPConnection(host, port)
137#                    conn.set_debuglevel(10)
138
139                creds = self.username + ":" + self.password
140                headers["Authorization"] = "Basic " + \
141                        creds.encode("base64").strip()
142                self.request(conn, method, url, body, headers, progress)
143                self.msg(u"waiting for status")
144                response = conn.getresponse()
145
146            if response.status == httplib.OK:
147                self.msg(u"reading response")
148                sys.stderr.flush()
149                response_body = response.read()
150            else:
151                raise HTTPError, (response.status, "%03i: %s (%s)" % (
152                    response.status, response.reason, response.read()))
153        finally:
154            conn.close()
155        return response_body
156
157    def create_changeset(self, created_by, comment):
158        if self.changeset is not None:
159            raise RuntimeError, "Changeset already opened"
160        self.progress_msg = u"I'm creating the changeset"
161        self.msg(u"")
162        root = ElementTree.Element("osm")
163        tree = ElementTree.ElementTree(root)
164        element = ElementTree.SubElement(root, "changeset")
165        ElementTree.SubElement(element, "tag", {"k": "created_by", "v": created_by})
166        ElementTree.SubElement(element, "tag", {"k": "comment", "v": comment})
167#       ElementTree.SubElement(element, "tag", {"k": "import", "v": "yes"})
168#       ElementTree.SubElement(element, "tag", {"k": "source", "v": u"BDLL25, EGRN, Instituto Geográfico Nacional"})
169#       ElementTree.SubElement(element, "tag", {"k": "revert", "v": "yes"})
170#       ElementTree.SubElement(element, "tag", {"k": "url", "v": "http://www.openstreetmap.org/user/nmixter/diary/8218"})
171        body = ElementTree.tostring(root, "utf-8")
172        reply = self._run_request("PUT", "/api/0.6/changeset/create", body)
173        changeset = int(reply.strip())
174        self.msg(u"done. Id: %i" % (changeset))
175        print >>sys.stderr, u""
176        self.changeset = changeset
177
178    def upload(self, change):
179        if self.changeset is None:
180            raise RuntimeError, "Changeset not opened"
181        self.progress_msg = u"Now I'm sending changes"
182        self.msg(u"")
183        for operation in change:
184            if operation.tag not in ("create", "modify", "delete"):
185                continue
186            for element in operation:
187                element.attrib["changeset"] = str(self.changeset)
188        body = ElementTree.tostring(change, "utf-8")
189        reply = self._run_request("POST", "/api/0.6/changeset/%i/upload"
190                                                % (self.changeset,), body, 1)
191        self.msg(u"done.")
192        print >>sys.stderr, u""
193        return reply
194
195    def close_changeset(self):
196        if self.changeset is None:
197            raise RuntimeError, "Changeset not opened"
198        self.progress_msg = u"Closing"
199        self.msg(u"")
200        reply = self._run_request("PUT", "/api/0.6/changeset/%i/close"
201                                                    % (self.changeset,))
202        self.changeset = None
203        self.msg(u"done, too.")
204        print >>sys.stderr, u""
205
206try:
207    this_dir = os.path.dirname(__file__)
208    try:
209        version = int(subprocess.Popen(["svnversion", this_dir], stdout = subprocess.PIPE).communicate()[0].strip())
210    except:
211        version = 1
212    if len(sys.argv) < 2:
213        print >>sys.stderr, u"Synopsis:"
214        print >>sys.stderr, u"    %s <file-name.osc> [<file-name.osc>...]"
215        sys.exit(1)
216
217    filenames = []
218    param = {}
219    num = 0
220    skip = 0
221    for arg in sys.argv[1:]:
222        num += 1
223        if skip:
224            skip -= 1
225            continue
226
227        if arg == "-u":
228            param['user'] = sys.argv[num + 1]
229            skip = 1
230        elif arg == "-p":
231            param['pass'] = sys.argv[num + 1]
232            skip = 1
233        elif arg == "-c":
234            param['confirm'] = sys.argv[num + 1]
235            skip = 1
236        elif arg == "-m":
237            param['comment'] = sys.argv[num + 1]
238            skip = 1
239        elif arg == "-s":
240            param['changeset'] = sys.argv[num + 1]
241            skip = 1
242        elif arg == "-n":
243            param['start'] = 1
244            skip = 0
245        else:
246            filenames.append(arg)
247
248    if 'user' in param:
249        login = param['user']
250    else:
251        login = raw_input("OSM login: ")
252    if not login:
253        sys.exit(1)
254    if 'pass' in param:
255        password = param['pass']
256    else:
257        password = raw_input("OSM password: ")
258    if not password:
259        sys.exit(1)
260
261    api = OSM_API(login, password)
262
263    changes = []
264    for filename in filenames:
265        if not os.path.exists(filename):
266            print >>sys.stderr, u"File %r doesn't exist!" % (filename,)
267            sys.exit(1)
268        tree = ElementTree.parse(filename)
269        root = tree.getroot()
270        if root.tag != "osmChange" or (root.attrib.get("version") != "0.3" and
271                root.attrib.get("version") != "0.6"):
272            print >>sys.stderr, u"File %s is not a v0.3 osmChange file!" % (filename,)
273            sys.exit(1)
274
275        if filename.endswith(".osc"):
276            diff_fn = filename[:-4] + ".diff.xml"
277        else:
278            diff_fn = filename + ".diff.xml"
279        if os.path.exists(diff_fn):
280            print >>sys.stderr, u"Diff file %r already exists, delete it " \
281                    "if you're sure you want to re-upload" % (diff_fn,)
282            sys.exit(1)
283
284        if filename.endswith(".osc"):
285            comment_fn = filename[:-4] + ".comment"
286        else:
287            comment_fn = filename + ".comment"
288        try:
289            comment_file = codecs.open(comment_fn, "r", "utf-8")
290            comment = comment_file.read().strip()
291            comment_file.close()
292        except IOError:
293            comment = None
294        if not comment:
295            if 'comment' in param:
296                comment = param['comment']
297            else:
298                comment = raw_input("Your comment to %r: " % (filename,))
299            if not comment:
300                sys.exit(1)
301            try:
302                comment = comment.decode(locale.getlocale()[1])
303            except TypeError:
304                comment = comment.decode("UTF-8")
305
306        print >>sys.stderr, u"     File: %r" % (filename,)
307        print >>sys.stderr, u"  Comment: %s" % (comment,)
308
309        if 'confirm' in param:
310            sure = param['confirm']
311        else:
312            print >>sys.stderr, u"Are you sure you want to send these changes?",
313            sure = raw_input()
314        if sure.lower() not in ("y", "yes"):
315            print >>sys.stderr, u"Skipping...\n"
316            continue
317        print >>sys.stderr, u""
318        if 'changeset' in param:
319            api.changeset = int(param['changeset'])
320        else:
321            api.create_changeset(u"upload.py v. %s" % (version,), comment)
322            if 'start' in param:
323                print api.changeset
324                sys.exit(0)
325        try:
326            diff_file = codecs.open(diff_fn, "w", "utf-8")
327            diff = api.upload(root)
328            diff_file.write(diff)
329            diff_file.close()
330        except HTTPError, (code, err):
331            sys.stderr.write("\n" + err + "\n")
332            if code in [ 404, 409, 412 ]: # Merge conflict
333                # TODO: also unlink when not the whole file has been uploaded
334                # because then likely the server will not be able to parse
335                # it and nothing gets committed
336                os.unlink(diff_fn)
337            sys.exit(1)
338        finally:
339            if 'changeset' not in param:
340                api.close_changeset()
341except HTTPError, (code, err):
342    sys.stderr.write(err)
343    sys.exit(1)
344except Exception, err:
345    print >>sys.stderr, repr(err)
346    traceback.print_exc(file=sys.stderr)
347    sys.exit(1)
Note: See TracBrowser for help on using the repository browser.