keep changesets open throughout subsequent uploads, unless we go past the timeout
[potlatch2.git] / net / systemeD / halcyon / connection / XMLConnection.as
1 package net.systemeD.halcyon.connection {
2
3     import flash.events.*;
4
5         import flash.system.Security;
6         import flash.net.*;
7     import org.iotashan.oauth.*;
8
9         import net.systemeD.halcyon.MapEvent;
10
11         public class XMLConnection extends XMLBaseConnection {
12
13         //public var readConnection:NetConnection;
14
15                 public function XMLConnection() {
16
17                         if (Connection.policyURL!='')
18                 Security.loadPolicyFile(Connection.policyURL);
19             var oauthPolicy:String = Connection.getParam("oauth_policy", "");
20             if ( oauthPolicy != "" ) {
21                 Security.loadPolicyFile(oauthPolicy);
22             }
23                 }
24                 
25                 override public function loadBbox(left:Number,right:Number,
26                                                                 top:Number,bottom:Number):void {
27             var mapVars:URLVariables = new URLVariables();
28             mapVars.bbox= left+","+bottom+","+right+","+top;
29
30             var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
31             mapRequest.data = mapVars;
32
33             var mapLoader:URLLoader = new URLLoader();
34             mapLoader.addEventListener(Event.COMPLETE, loadedMap);
35             mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
36             mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
37             mapLoader.load(mapRequest);
38             dispatchEvent(new Event(LOAD_STARTED));
39                 }
40
41         private function errorOnMapLoad(event:Event):void {
42                         dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map" } ));
43         }
44         private function mapLoadStatus(event:HTTPStatusEvent):void {
45             trace("loading map status = "+event.status);
46         }
47
48         protected var appID:OAuthConsumer;
49         protected var authToken:OAuthToken;
50         
51             override public function setAppID(id:Object):void {
52                 appID = OAuthConsumer(id);
53             }
54             
55             override public function setAuthToken(id:Object):void {
56                 authToken = OAuthToken(id);
57             }
58
59         private var httpStatus:int = 0;
60         
61         private function recordStatus(event:HTTPStatusEvent):void {
62             httpStatus = event.status;
63         }
64         
65         private var lastUploadedChangesetTags:Object;
66         
67         override public function createChangeset(tags:Object):void {
68             lastUploadedChangesetTags = tags;
69             
70                 var changesetXML:XML = <osm version="0.6"><changeset /></osm>;
71                 var changeset:XML = <changeset />;
72                 for (var tagKey:Object in tags) {
73               var tagXML:XML = <tag/>;
74               tagXML.@k = tagKey;
75               tagXML.@v = tags[tagKey];
76               changesetXML.changeset.appendChild(tagXML);
77             }        
78
79             // make an OAuth query
80             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
81             var url:String = Connection.apiBaseURL+"changeset/create";
82             //var params:Object = { _method: "PUT" };
83             var oauthRequest:OAuthRequest = new OAuthRequest("PUT", url, null, appID, authToken);
84             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
85
86             // build the actual request
87             var urlReq:URLRequest = new URLRequest(String(urlStr));
88             urlReq.method = "POST";
89             urlReq.data = changesetXML.toXMLString();
90             urlReq.contentType = "application/xml";
91             urlReq.requestHeaders = new Array(new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"));
92             var loader:URLLoader = new URLLoader();
93             loader.addEventListener(Event.COMPLETE, changesetCreateComplete);
94             loader.addEventListener(IOErrorEvent.IO_ERROR, changesetCreateError);
95             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
96                 loader.load(urlReq);
97             }
98
99         private function changesetCreateComplete(event:Event):void {
100             // response should be a Number changeset id
101             var id:Number = Number(URLLoader(event.target).data);
102             
103             // which means we now have a new changeset!
104             setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
105         }
106
107         private function changesetCreateError(event:IOErrorEvent):void {
108             dispatchEvent(new Event(NEW_CHANGESET_ERROR));
109         }
110         
111         override public function uploadChanges():void {
112             var changeset:Changeset = getActiveChangeset();
113             var upload:XML = <osmChange version="0.6"/>
114             upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode));
115             upload.appendChild(addCreated(changeset, getAllWayIDs, getWay, serialiseWay));
116             upload.appendChild(addCreated(changeset, getAllRelationIDs, getRelation, serialiseRelation));
117             upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
118             upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
119             upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
120             upload.appendChild(addDeleted(changeset, getAllRelationIDs, getRelation, serialiseEntityRoot));
121             upload.appendChild(addDeleted(changeset, getAllWayIDs, getWay, serialiseEntityRoot));
122             upload.appendChild(addDeleted(changeset, getAllNodeIDs, getNode, serialiseEntityRoot));
123
124             // now actually upload them
125             // make an OAuth query
126             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
127             var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
128             var oauthRequest:OAuthRequest = new OAuthRequest("POST", url, null, appID, authToken);
129             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
130
131             // build the actual request
132             var urlReq:URLRequest = new URLRequest(String(urlStr));
133             urlReq.method = "POST";
134             urlReq.data = upload.toXMLString();
135             urlReq.contentType = "text/xml";
136             var loader:URLLoader = new URLLoader();
137             loader.addEventListener(Event.COMPLETE, diffUploadComplete);
138             loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
139             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
140                 loader.load(urlReq);
141                 
142                 dispatchEvent(new Event(SAVE_STARTED));
143         }
144
145         private function diffUploadComplete(event:Event):void {
146             // response should be XML describing the progress
147             var results:XML = new XML((URLLoader(event.target).data));
148             
149             for each( var update:XML in results.child("*") ) {
150                 var oldID:Number = Number(update.@old_id);
151                 var newID:Number = Number(update.@new_id);
152                 var version:uint = uint(update.@new_version);
153                 var type:String = update.name();
154
155                                 if (newID==0) {
156                                         // delete
157                         if ( type == "node" ) killNode(oldID);
158                         else if ( type == "way" ) killWay(oldID);
159                         else if ( type == "relation" ) killRelation(oldID);
160                                         
161                                 } else {
162                                         // create/update
163                         var entity:Entity;
164                         if ( type == "node" ) entity = getNode(oldID);
165                         else if ( type == "way" ) entity = getWay(oldID);
166                         else if ( type == "relation" ) entity = getRelation(oldID);
167                         entity.markClean(newID, version);
168                 
169                         if ( oldID != newID ) {
170                             if ( type == "node" ) renumberNode(oldID, entity as Node, false);
171                             else if ( type == "way" ) renumberWay(oldID, entity as Way, false);
172                             else if ( type == "relation" ) renumberRelation(oldID, entity as Relation, false);
173                         }
174                                 }
175             }
176
177             dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
178                         freshenActiveChangeset();
179             markClean(); // marks the connection clean. Pressing undo from this point on leads to unexpected results
180             MainUndoStack.getGlobalStack().breakUndo(); // so, for now, break the undo stack
181         }
182
183         private function diffUploadError(event:IOErrorEvent):void {
184                         dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't upload data: "+httpStatus+" "+event.text } ));
185                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
186         }
187
188         private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
189             var create:XML = <create version="0.6"/>
190             for each( var id:Number in getIDs() ) {
191                 var entity:Entity = get(id);
192                 if ( id >= 0 || entity.deleted )
193                     continue;
194                     
195                 var xml:XML = serialise(entity);
196                 xml.@changeset = changeset.id;
197                 create.appendChild(xml);
198             }
199             return create.hasComplexContent() ? create : <!-- blank create section -->;
200         }
201
202                 private function addDeleted(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
203             var del:XML = <delete version="0.6"/>
204             for each( var id:Number in getIDs() ) {
205                 var entity:Entity = get(id);
206                 // creates are already included
207                 if ( id < 0 || !entity.deleted )
208                     continue;
209                     
210                 var xml:XML = serialise(entity);
211                 xml.@changeset = changeset.id;
212                 del.appendChild(xml);
213             }
214             return del.hasComplexContent() ? del : <!-- blank delete section -->;
215                 }
216
217         private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
218             var modify:XML = <modify version="0.6"/>
219             for each( var id:Number in getIDs() ) {
220                 var entity:Entity = get(id);
221                 // creates and deletes are already included
222                 if ( id < 0 || entity.deleted || !entity.isDirty )
223                     continue;
224                     
225                 var xml:XML = serialise(entity);
226                 xml.@changeset = changeset.id;
227                 modify.appendChild(xml);
228             }
229             return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
230         }
231
232         private function serialiseNode(node:Node):XML {
233             var xml:XML = serialiseEntityRoot(node); //<node/>
234             serialiseEntityTags(node, xml);
235             xml.@lat = node.lat;
236             xml.@lon = node.lon;
237             return xml;
238         }
239
240         private function serialiseWay(way:Way):XML {
241             var xml:XML = serialiseEntityRoot(way); //<node/>
242             serialiseEntityTags(way, xml);
243             for ( var i:uint = 0; i < way.length; i++ ) {
244                 var nd:XML = <nd/>
245                 nd.@ref = way.getNode(i).id;
246                 xml.appendChild(nd);
247             }
248             return xml;
249         }
250
251         private function serialiseRelation(relation:Relation):XML {
252             var xml:XML = serialiseEntityRoot(relation); //<node/>
253             serialiseEntityTags(relation, xml);
254             for ( var i:uint = 0; i < relation.length; i++ ) {
255                 var relMember:RelationMember = relation.getMember(i);
256                 var member:XML = <member/>
257                 member.@ref = relMember.entity.id;
258                 member.@type = relMember.entity.getType();
259                 member.@role = relMember.role;
260                 xml.appendChild(member);
261             }
262             return xml;
263         }
264         
265                 private function serialiseEntityRoot(entity:Object):XML {
266                         var xml:XML;
267                         if      (entity is Way     ) { xml = <way/> }
268                         else if (entity is Node    ) { xml = <node/> }
269                         else if (entity is Relation) { xml = <relation/> }
270                         xml.@id = entity.id;
271                         xml.@version = entity.version;
272                         return xml;
273                 }
274
275         private function serialiseEntityTags(entity:Entity, xml:XML):void {
276             xml.@id = entity.id;
277             xml.@version = entity.version;
278             for each( var tag:Tag in entity.getTagArray() ) {
279               var tagXML:XML = <tag/>
280               tagXML.@k = tag.key;
281               tagXML.@v = tag.value;
282               xml.appendChild(tagXML);
283             }
284         }
285
286         }
287 }