1 package net.systemeD.halcyon.connection {
5 import flash.system.Security;
7 import org.iotashan.oauth.*;
9 import net.systemeD.halcyon.MapEvent;
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
15 public class XMLConnection extends XMLBaseConnection {
17 //public var readConnection:NetConnection;
19 public function XMLConnection() {
21 if (Connection.policyURL!='')
22 Security.loadPolicyFile(Connection.policyURL);
23 var oauthPolicy:String = Connection.getParam("oauth_policy", "");
24 if ( oauthPolicy != "" ) {
25 Security.loadPolicyFile(oauthPolicy);
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;
34 var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
35 mapRequest.data = mapVars;
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));
45 private function errorOnMapLoad(event:Event):void {
46 dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map" } ));
48 private function mapLoadStatus(event:HTTPStatusEvent):void {
49 trace("loading map status = "+event.status);
52 protected var appID:OAuthConsumer;
53 protected var authToken:OAuthToken;
55 override public function setAppID(id:Object):void {
56 appID = OAuthConsumer(id);
59 override public function setAuthToken(id:Object):void {
60 authToken = OAuthToken(id);
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);
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"];
73 if ( key == null || secret == null )
76 return new OAuthToken(key, secret);
79 private var httpStatus:int = 0;
81 private function recordStatus(event:HTTPStatusEvent):void {
82 httpStatus = event.status;
85 private var lastUploadedChangesetTags:Object;
87 override public function createChangeset(tags:Object):void {
88 lastUploadedChangesetTags = tags;
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/>;
95 tagXML.@v = tags[tagKey];
96 changesetXML.changeset.appendChild(tagXML);
99 sendOAuthPut(Connection.apiBaseURL+"changeset/create",
101 changesetCreateComplete, changesetCreateError, recordStatus);
104 private function changesetCreateComplete(event:Event):void {
105 // response should be a Number changeset id
106 var id:Number = Number(URLLoader(event.target).data);
108 // which means we now have a new changeset!
109 setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
112 private function changesetCreateError(event:IOErrorEvent):void {
113 dispatchEvent(new Event(NEW_CHANGESET_ERROR));
116 override public function closeChangeset():void {
117 var cs:Changeset = getActiveChangeset();
120 sendOAuthPut(Connection.apiBaseURL+"changeset/"+cs.id+"/close",
122 changesetCloseComplete, changesetCloseError, recordStatus);
123 closeActiveChangeset();
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
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);
138 private function sendOAuthPut(url:String, xml:XML, onComplete:Function, onError:Function, onStatus:Function):void {
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);
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);
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));
175 // now actually upload them
176 // make an OAuth query
178 var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
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);
191 dispatchEvent(new Event(SAVE_STARTED));
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));
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();
206 if ( type == "node" ) killNode(oldID);
207 else if ( type == "way" ) killWay(oldID);
208 else if ( type == "relation" ) killRelation(oldID);
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);
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);
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
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));
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 )
244 var xml:XML = serialise(entity);
245 xml.@changeset = changeset.id;
246 create.appendChild(xml);
248 return create.hasComplexContent() ? create : <!-- blank create section -->;
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 )
259 var xml:XML = serialise(entity);
260 xml.@changeset = changeset.id;
261 del.appendChild(xml);
263 return del.hasComplexContent() ? del : <!-- blank delete section -->;
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 )
274 var xml:XML = serialise(entity);
275 xml.@changeset = changeset.id;
276 modify.appendChild(xml);
278 return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
281 private function serialiseNode(node:Node):XML {
282 var xml:XML = serialiseEntityRoot(node); //<node/>
283 serialiseEntityTags(node, xml);
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++ ) {
294 nd.@ref = way.getNode(i).id;
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);
314 private function serialiseEntityRoot(entity:Object):XML {
316 if (entity is Way ) { xml = <way/> }
317 else if (entity is Node ) { xml = <node/> }
318 else if (entity is Relation) { xml = <relation/> }
320 xml.@version = entity.version;
324 private function serialiseEntityTags(entity:Entity, xml:XML):void {
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);
332 var tagXML:XML = <tag/>
334 tagXML.@v = tag.value;
335 xml.appendChild(tagXML);
339 override public function fetchUserTraces(refresh:Boolean=false):void {
340 if (traces_loaded && !refresh) {
341 dispatchEvent(new Event(TRACES_LOADED));
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?
349 private function tracesLoadComplete(event:Event):void {
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);
356 traces_loaded = true;
357 dispatchEvent(new Event(LOAD_COMPLETED));
358 dispatchEvent(new Event(TRACES_LOADED));
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?