Provide some documentation as to what each of the 4 Connection classes are for.
[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     /**
12     * XMLConnection provides all the methods required to connect to a live
13     * OSM server. See OSMConnection for connecting to a read-only .osm file
14     */
15         public class XMLConnection extends XMLBaseConnection {
16
17         //public var readConnection:NetConnection;
18
19                 public function XMLConnection() {
20
21                         if (Connection.policyURL!='')
22                 Security.loadPolicyFile(Connection.policyURL);
23             var oauthPolicy:String = Connection.getParam("oauth_policy", "");
24             if ( oauthPolicy != "" ) {
25                 Security.loadPolicyFile(oauthPolicy);
26             }
27                 }
28                 
29                 override public function loadBbox(left:Number,right:Number,
30                                                                 top:Number,bottom:Number):void {
31             var mapVars:URLVariables = new URLVariables();
32             mapVars.bbox= left+","+bottom+","+right+","+top;
33
34             var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
35             mapRequest.data = mapVars;
36
37             var mapLoader:URLLoader = new URLLoader();
38             mapLoader.addEventListener(Event.COMPLETE, loadedMap);
39             mapLoader.addEventListener(IOErrorEvent.IO_ERROR, errorOnMapLoad);
40             mapLoader.addEventListener(HTTPStatusEvent.HTTP_STATUS, mapLoadStatus);
41             mapLoader.load(mapRequest);
42             dispatchEvent(new Event(LOAD_STARTED));
43                 }
44
45         private function errorOnMapLoad(event:Event):void {
46                         dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map" } ));
47         }
48         private function mapLoadStatus(event:HTTPStatusEvent):void {
49             trace("loading map status = "+event.status);
50         }
51
52         protected var appID:OAuthConsumer;
53         protected var authToken:OAuthToken;
54         
55             override public function setAppID(id:Object):void {
56                 appID = OAuthConsumer(id);
57             }
58             
59             override public function setAuthToken(id:Object):void {
60                 authToken = OAuthToken(id);
61             }
62
63         private var httpStatus:int = 0;
64         
65         private function recordStatus(event:HTTPStatusEvent):void {
66             httpStatus = event.status;
67         }
68         
69         private var lastUploadedChangesetTags:Object;
70         
71         override public function createChangeset(tags:Object):void {
72             lastUploadedChangesetTags = tags;
73             
74                 var changesetXML:XML = <osm version="0.6"><changeset /></osm>;
75                 var changeset:XML = <changeset />;
76                 for (var tagKey:Object in tags) {
77               var tagXML:XML = <tag/>;
78               tagXML.@k = tagKey;
79               tagXML.@v = tags[tagKey];
80               changesetXML.changeset.appendChild(tagXML);
81             }        
82
83                         sendOAuthPut(Connection.apiBaseURL+"changeset/create",
84                                                  changesetXML,
85                                                  changesetCreateComplete, changesetCreateError, recordStatus);
86             }
87
88         private function changesetCreateComplete(event:Event):void {
89             // response should be a Number changeset id
90             var id:Number = Number(URLLoader(event.target).data);
91             
92             // which means we now have a new changeset!
93             setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
94         }
95
96         private function changesetCreateError(event:IOErrorEvent):void {
97             dispatchEvent(new Event(NEW_CHANGESET_ERROR));
98         }
99
100                 override public function closeChangeset():void {
101             var cs:Changeset = getActiveChangeset();
102                         if (!cs) return;
103                         
104                         sendOAuthPut(Connection.apiBaseURL+"changeset/"+cs.id+"/close",
105                                                  null,
106                                                  changesetCloseComplete, changesetCloseError, recordStatus);
107                         closeActiveChangeset();
108                 }
109                 
110                 private function changesetCloseComplete(event:Event):void { }
111                 private function changesetCloseError(event:Event):void { }
112                 // ** TODO: when we get little floating warnings, we can send a happy or sad one up
113
114         private function signedOAuthURL(url:String, method:String):String {
115             // method should be PUT, GET, POST or DELETE
116             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
117             var oauthRequest:OAuthRequest = new OAuthRequest(method, url, null, appID, authToken);
118             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING);
119             return String(urlStr);
120         }
121
122                 private function sendOAuthPut(url:String, xml:XML, onComplete:Function, onError:Function, onStatus:Function):void {
123             // build the request
124             var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "PUT"));
125             urlReq.method = "POST";
126                         if (xml) { urlReq.data = xml.toXMLString(); } else { urlReq.data = true; }
127             urlReq.contentType = "application/xml";
128             urlReq.requestHeaders = new Array(new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"));
129             var loader:URLLoader = new URLLoader();
130             loader.addEventListener(Event.COMPLETE, onComplete);
131             loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
132             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
133                 loader.load(urlReq);
134                 }
135
136         private function sendOAuthGet(url:String, onComplete:Function, onError:Function, onStatus:Function):void {
137             var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "GET"));
138             urlReq.method = "GET";
139             var loader:URLLoader = new URLLoader();
140             loader.addEventListener(Event.COMPLETE, onComplete);
141             loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
142             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
143             loader.load(urlReq);
144         }
145
146         override public function signOAuthGet(url:String):String {
147             return signedOAuthURL(url, "GET");
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
166             var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
167
168             // build the actual request
169             var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "POST"));
170             urlReq.method = "POST";
171             urlReq.data = upload.toXMLString();
172             urlReq.contentType = "text/xml";
173             var loader:URLLoader = new URLLoader();
174             loader.addEventListener(Event.COMPLETE, diffUploadComplete);
175             loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
176             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
177                 loader.load(urlReq);
178                 
179                 dispatchEvent(new Event(SAVE_STARTED));
180         }
181
182         private function diffUploadComplete(event:Event):void {
183             // response should be XML describing the progress
184             var results:XML = new XML((URLLoader(event.target).data));
185             
186             for each( var update:XML in results.child("*") ) {
187                 var oldID:Number = Number(update.@old_id);
188                 var newID:Number = Number(update.@new_id);
189                 var version:uint = uint(update.@new_version);
190                 var type:String = update.name();
191
192                                 if (newID==0) {
193                                         // delete
194                         if ( type == "node" ) killNode(oldID);
195                         else if ( type == "way" ) killWay(oldID);
196                         else if ( type == "relation" ) killRelation(oldID);
197                                         
198                                 } else {
199                                         // create/update
200                         var entity:Entity;
201                         if ( type == "node" ) entity = getNode(oldID);
202                         else if ( type == "way" ) entity = getWay(oldID);
203                         else if ( type == "relation" ) entity = getRelation(oldID);
204                         entity.markClean(newID, version);
205                 
206                         if ( oldID != newID ) {
207                             if ( type == "node" ) renumberNode(oldID, entity as Node, false);
208                             else if ( type == "way" ) renumberWay(oldID, entity as Way, false);
209                             else if ( type == "relation" ) renumberRelation(oldID, entity as Relation, false);
210                         }
211                                 }
212             }
213
214             dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
215                         freshenActiveChangeset();
216             markClean(); // marks the connection clean. Pressing undo from this point on leads to unexpected results
217             MainUndoStack.getGlobalStack().breakUndo(); // so, for now, break the undo stack
218         }
219
220         private function diffUploadError(event:IOErrorEvent):void {
221                         dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't upload data: "+httpStatus+" "+event.text } ));
222                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
223         }
224
225         private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
226             var create:XML = <create version="0.6"/>
227             for each( var id:Number in getIDs() ) {
228                 var entity:Entity = get(id);
229                 if ( id >= 0 || entity.deleted )
230                     continue;
231                     
232                 var xml:XML = serialise(entity);
233                 xml.@changeset = changeset.id;
234                 create.appendChild(xml);
235             }
236             return create.hasComplexContent() ? create : <!-- blank create section -->;
237         }
238
239                 private function addDeleted(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
240             var del:XML = <delete version="0.6"/>
241             for each( var id:Number in getIDs() ) {
242                 var entity:Entity = get(id);
243                 // creates are already included
244                 if ( id < 0 || !entity.deleted )
245                     continue;
246                     
247                 var xml:XML = serialise(entity);
248                 xml.@changeset = changeset.id;
249                 del.appendChild(xml);
250             }
251             return del.hasComplexContent() ? del : <!-- blank delete section -->;
252                 }
253
254         private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
255             var modify:XML = <modify version="0.6"/>
256             for each( var id:Number in getIDs() ) {
257                 var entity:Entity = get(id);
258                 // creates and deletes are already included
259                 if ( id < 0 || entity.deleted || !entity.isDirty )
260                     continue;
261                     
262                 var xml:XML = serialise(entity);
263                 xml.@changeset = changeset.id;
264                 modify.appendChild(xml);
265             }
266             return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
267         }
268
269         private function serialiseNode(node:Node):XML {
270             var xml:XML = serialiseEntityRoot(node); //<node/>
271             serialiseEntityTags(node, xml);
272             xml.@lat = node.lat;
273             xml.@lon = node.lon;
274             return xml;
275         }
276
277         private function serialiseWay(way:Way):XML {
278             var xml:XML = serialiseEntityRoot(way); //<node/>
279             serialiseEntityTags(way, xml);
280             for ( var i:uint = 0; i < way.length; i++ ) {
281                 var nd:XML = <nd/>
282                 nd.@ref = way.getNode(i).id;
283                 xml.appendChild(nd);
284             }
285             return xml;
286         }
287
288         private function serialiseRelation(relation:Relation):XML {
289             var xml:XML = serialiseEntityRoot(relation); //<node/>
290             serialiseEntityTags(relation, xml);
291             for ( var i:uint = 0; i < relation.length; i++ ) {
292                 var relMember:RelationMember = relation.getMember(i);
293                 var member:XML = <member/>
294                 member.@ref = relMember.entity.id;
295                 member.@type = relMember.entity.getType();
296                 member.@role = relMember.role;
297                 xml.appendChild(member);
298             }
299             return xml;
300         }
301         
302                 private function serialiseEntityRoot(entity:Object):XML {
303                         var xml:XML;
304                         if      (entity is Way     ) { xml = <way/> }
305                         else if (entity is Node    ) { xml = <node/> }
306                         else if (entity is Relation) { xml = <relation/> }
307                         xml.@id = entity.id;
308                         xml.@version = entity.version;
309                         return xml;
310                 }
311
312         private function serialiseEntityTags(entity:Entity, xml:XML):void {
313             xml.@id = entity.id;
314             xml.@version = entity.version;
315             for each( var tag:Tag in entity.getTagArray() ) {
316               var tagXML:XML = <tag/>
317               tagXML.@k = tag.key;
318               tagXML.@v = tag.value;
319               xml.appendChild(tagXML);
320             }
321         }
322
323         override public function fetchUserTraces():void {
324             sendOAuthGet(Connection.apiBaseURL+"user/gpx_files",
325                          tracesLoadComplete, errorOnMapLoad, mapLoadStatus); //needs error handlers
326             dispatchEvent(new Event(LOAD_STARTED)); //specific to map or reusable?
327         }
328
329         private function tracesLoadComplete(event:Event):void {
330             clearTraces();
331             var files:XML = new XML(URLLoader(event.target).data);
332             for each(var traceData:XML in files.gpx_file) {
333               var t:Object = {};
334               t.id = traceData.@id;
335               t.name = traceData.@name;
336               t.description = traceData.description;
337               var tags:Array = [];
338               for each(var tag:XML in traceData.tag) {
339                 tags.push(String(tag));
340               }
341               t.tags = tags.join(" ");
342               t.url = Connection.apiBaseURL+"gpx/"+t.id+"/data";
343               addTrace(t);
344             }
345             trace("loaded gpx files");
346             dispatchEvent(new Event(LOAD_COMPLETED));
347             dispatchEvent(new Event(TRACES_LOADED));
348         }
349         }
350 }