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