Use tags and descriptions
[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                         sendOAuthPut(Connection.apiBaseURL+"changeset/create",
80                                                  changesetXML,
81                                                  changesetCreateComplete, changesetCreateError, recordStatus);
82             }
83
84         private function changesetCreateComplete(event:Event):void {
85             // response should be a Number changeset id
86             var id:Number = Number(URLLoader(event.target).data);
87             
88             // which means we now have a new changeset!
89             setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
90         }
91
92         private function changesetCreateError(event:IOErrorEvent):void {
93             dispatchEvent(new Event(NEW_CHANGESET_ERROR));
94         }
95
96                 override public function closeChangeset():void {
97             var cs:Changeset = getActiveChangeset();
98                         if (!cs) return;
99                         
100                         sendOAuthPut(Connection.apiBaseURL+"changeset/"+cs.id+"/close",
101                                                  null,
102                                                  changesetCloseComplete, changesetCloseError, recordStatus);
103                         closeActiveChangeset();
104                 }
105                 
106                 private function changesetCloseComplete(event:Event):void { }
107                 private function changesetCloseError(event:Event):void { }
108                 // ** TODO: when we get little floating warnings, we can send a happy or sad one up
109
110                 private function sendOAuthPut(url:String, xml:XML, onComplete:Function, onError:Function, onStatus:Function):void {
111             // make an OAuth query
112             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
113             var oauthRequest:OAuthRequest = new OAuthRequest("PUT", url, null, appID, authToken);
114             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
115
116             // build the actual request
117             var urlReq:URLRequest = new URLRequest(String(urlStr));
118             urlReq.method = "POST";
119                         if (xml) { urlReq.data = xml.toXMLString(); } else { urlReq.data = true; }
120             urlReq.contentType = "application/xml";
121             urlReq.requestHeaders = new Array(new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"));
122             var loader:URLLoader = new URLLoader();
123             loader.addEventListener(Event.COMPLETE, onComplete);
124             loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
125             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
126                 loader.load(urlReq);
127                 }
128
129         private function sendOAuthGet(url:String, onComplete:Function, onError:Function, onStatus:Function):void {
130             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
131             var oauthRequest:OAuthRequest = new OAuthRequest("GET", url, null, appID, authToken);
132             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING);
133
134             var urlReq:URLRequest = new URLRequest(String(urlStr));
135             urlReq.method = "GET";
136             var loader:URLLoader = new URLLoader();
137             loader.addEventListener(Event.COMPLETE, onComplete);
138             loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
139             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
140             loader.load(urlReq);
141         }
142
143         override public function signOAuthGet(url:String):String {
144             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
145             var oauthRequest:OAuthRequest = new OAuthRequest("GET", url, null, appID, authToken);
146             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING);
147             return String(urlStr);
148         }
149         
150         override public function uploadChanges():void {
151             var changeset:Changeset = getActiveChangeset();
152             var upload:XML = <osmChange version="0.6"/>
153             upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode));
154             upload.appendChild(addCreated(changeset, getAllWayIDs, getWay, serialiseWay));
155             upload.appendChild(addCreated(changeset, getAllRelationIDs, getRelation, serialiseRelation));
156             upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
157             upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
158             upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
159             upload.appendChild(addDeleted(changeset, getAllRelationIDs, getRelation, serialiseEntityRoot));
160             upload.appendChild(addDeleted(changeset, getAllWayIDs, getWay, serialiseEntityRoot));
161             upload.appendChild(addDeleted(changeset, getAllNodeIDs, getNode, serialiseEntityRoot));
162
163             // now actually upload them
164             // make an OAuth query
165             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
166             var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
167             var oauthRequest:OAuthRequest = new OAuthRequest("POST", url, null, appID, authToken);
168             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
169
170             // build the actual request
171             var urlReq:URLRequest = new URLRequest(String(urlStr));
172             urlReq.method = "POST";
173             urlReq.data = upload.toXMLString();
174             urlReq.contentType = "text/xml";
175             var loader:URLLoader = new URLLoader();
176             loader.addEventListener(Event.COMPLETE, diffUploadComplete);
177             loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
178             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
179                 loader.load(urlReq);
180                 
181                 dispatchEvent(new Event(SAVE_STARTED));
182         }
183
184         private function diffUploadComplete(event:Event):void {
185             // response should be XML describing the progress
186             var results:XML = new XML((URLLoader(event.target).data));
187             
188             for each( var update:XML in results.child("*") ) {
189                 var oldID:Number = Number(update.@old_id);
190                 var newID:Number = Number(update.@new_id);
191                 var version:uint = uint(update.@new_version);
192                 var type:String = update.name();
193
194                                 if (newID==0) {
195                                         // delete
196                         if ( type == "node" ) killNode(oldID);
197                         else if ( type == "way" ) killWay(oldID);
198                         else if ( type == "relation" ) killRelation(oldID);
199                                         
200                                 } else {
201                                         // create/update
202                         var entity:Entity;
203                         if ( type == "node" ) entity = getNode(oldID);
204                         else if ( type == "way" ) entity = getWay(oldID);
205                         else if ( type == "relation" ) entity = getRelation(oldID);
206                         entity.markClean(newID, version);
207                 
208                         if ( oldID != newID ) {
209                             if ( type == "node" ) renumberNode(oldID, entity as Node, false);
210                             else if ( type == "way" ) renumberWay(oldID, entity as Way, false);
211                             else if ( type == "relation" ) renumberRelation(oldID, entity as Relation, false);
212                         }
213                                 }
214             }
215
216             dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
217                         freshenActiveChangeset();
218             markClean(); // marks the connection clean. Pressing undo from this point on leads to unexpected results
219             MainUndoStack.getGlobalStack().breakUndo(); // so, for now, break the undo stack
220         }
221
222         private function diffUploadError(event:IOErrorEvent):void {
223                         dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't upload data: "+httpStatus+" "+event.text } ));
224                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
225         }
226
227         private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
228             var create:XML = <create version="0.6"/>
229             for each( var id:Number in getIDs() ) {
230                 var entity:Entity = get(id);
231                 if ( id >= 0 || entity.deleted )
232                     continue;
233                     
234                 var xml:XML = serialise(entity);
235                 xml.@changeset = changeset.id;
236                 create.appendChild(xml);
237             }
238             return create.hasComplexContent() ? create : <!-- blank create section -->;
239         }
240
241                 private function addDeleted(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
242             var del:XML = <delete version="0.6"/>
243             for each( var id:Number in getIDs() ) {
244                 var entity:Entity = get(id);
245                 // creates are already included
246                 if ( id < 0 || !entity.deleted )
247                     continue;
248                     
249                 var xml:XML = serialise(entity);
250                 xml.@changeset = changeset.id;
251                 del.appendChild(xml);
252             }
253             return del.hasComplexContent() ? del : <!-- blank delete section -->;
254                 }
255
256         private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
257             var modify:XML = <modify version="0.6"/>
258             for each( var id:Number in getIDs() ) {
259                 var entity:Entity = get(id);
260                 // creates and deletes are already included
261                 if ( id < 0 || entity.deleted || !entity.isDirty )
262                     continue;
263                     
264                 var xml:XML = serialise(entity);
265                 xml.@changeset = changeset.id;
266                 modify.appendChild(xml);
267             }
268             return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
269         }
270
271         private function serialiseNode(node:Node):XML {
272             var xml:XML = serialiseEntityRoot(node); //<node/>
273             serialiseEntityTags(node, xml);
274             xml.@lat = node.lat;
275             xml.@lon = node.lon;
276             return xml;
277         }
278
279         private function serialiseWay(way:Way):XML {
280             var xml:XML = serialiseEntityRoot(way); //<node/>
281             serialiseEntityTags(way, xml);
282             for ( var i:uint = 0; i < way.length; i++ ) {
283                 var nd:XML = <nd/>
284                 nd.@ref = way.getNode(i).id;
285                 xml.appendChild(nd);
286             }
287             return xml;
288         }
289
290         private function serialiseRelation(relation:Relation):XML {
291             var xml:XML = serialiseEntityRoot(relation); //<node/>
292             serialiseEntityTags(relation, xml);
293             for ( var i:uint = 0; i < relation.length; i++ ) {
294                 var relMember:RelationMember = relation.getMember(i);
295                 var member:XML = <member/>
296                 member.@ref = relMember.entity.id;
297                 member.@type = relMember.entity.getType();
298                 member.@role = relMember.role;
299                 xml.appendChild(member);
300             }
301             return xml;
302         }
303         
304                 private function serialiseEntityRoot(entity:Object):XML {
305                         var xml:XML;
306                         if      (entity is Way     ) { xml = <way/> }
307                         else if (entity is Node    ) { xml = <node/> }
308                         else if (entity is Relation) { xml = <relation/> }
309                         xml.@id = entity.id;
310                         xml.@version = entity.version;
311                         return xml;
312                 }
313
314         private function serialiseEntityTags(entity:Entity, xml:XML):void {
315             xml.@id = entity.id;
316             xml.@version = entity.version;
317             for each( var tag:Tag in entity.getTagArray() ) {
318               var tagXML:XML = <tag/>
319               tagXML.@k = tag.key;
320               tagXML.@v = tag.value;
321               xml.appendChild(tagXML);
322             }
323         }
324
325         override public function fetchUserTraces():void {
326             sendOAuthGet(Connection.apiBaseURL+"user/gpx_files",
327                          tracesLoadComplete, errorOnMapLoad, mapLoadStatus); //needs error handlers
328             dispatchEvent(new Event(LOAD_STARTED)); //specific to map or reusable?
329         }
330
331         private function tracesLoadComplete(event:Event):void {
332             clearTraces();
333             var files:XML = new XML(URLLoader(event.target).data);
334             for each(var traceData:XML in files.gpx_file) {
335               var t:Object = {};
336               t.id = traceData.@id;
337               t.name = traceData.@name;
338               t.description = traceData.description;
339               var tags:Array = [];
340               for each(var tag:XML in traceData.tag) {
341                 tags.push(String(tag));
342               }
343               t.tags = tags.join(" ");
344               t.url = Connection.apiBaseURL+"gpx/"+t.id+"/data";
345               addTrace(t);
346             }
347             trace("loaded gpx files");
348             dispatchEvent(new Event(LOAD_COMPLETED));
349             dispatchEvent(new Event(TRACES_LOADED));
350         }
351         }
352 }