1 package net.systemeD.halcyon.connection {
5 import flash.system.Security;
7 import org.iotashan.oauth.*;
9 import net.systemeD.halcyon.MapEvent;
11 public class XMLConnection extends XMLBaseConnection {
13 //public var readConnection:NetConnection;
15 public function XMLConnection() {
17 if (Connection.policyURL!='')
18 Security.loadPolicyFile(Connection.policyURL);
19 var oauthPolicy:String = Connection.getParam("oauth_policy", "");
20 if ( oauthPolicy != "" ) {
21 Security.loadPolicyFile(oauthPolicy);
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;
30 var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
31 mapRequest.data = mapVars;
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));
41 private function errorOnMapLoad(event:Event):void {
42 dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't load the map" } ));
44 private function mapLoadStatus(event:HTTPStatusEvent):void {
45 trace("loading map status = "+event.status);
48 protected var appID:OAuthConsumer;
49 protected var authToken:OAuthToken;
51 override public function setAppID(id:Object):void {
52 appID = OAuthConsumer(id);
55 override public function setAuthToken(id:Object):void {
56 authToken = OAuthToken(id);
59 private var httpStatus:int = 0;
61 private function recordStatus(event:HTTPStatusEvent):void {
62 httpStatus = event.status;
65 private var lastUploadedChangesetTags:Object;
67 override public function createChangeset(tags:Object):void {
68 lastUploadedChangesetTags = tags;
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/>;
75 tagXML.@v = tags[tagKey];
76 changesetXML.changeset.appendChild(tagXML);
79 sendOAuthPut(Connection.apiBaseURL+"changeset/create",
81 changesetCreateComplete, changesetCreateError, recordStatus);
84 private function changesetCreateComplete(event:Event):void {
85 // response should be a Number changeset id
86 var id:Number = Number(URLLoader(event.target).data);
88 // which means we now have a new changeset!
89 setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
92 private function changesetCreateError(event:IOErrorEvent):void {
93 dispatchEvent(new Event(NEW_CHANGESET_ERROR));
96 override public function closeChangeset():void {
97 var cs:Changeset = getActiveChangeset();
100 sendOAuthPut(Connection.apiBaseURL+"changeset/"+cs.id+"/close",
102 changesetCloseComplete, changesetCloseError, recordStatus);
103 closeActiveChangeset();
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
110 private function signedOAuthURL(url:String, method:String):String {
111 // method should be PUT, GET, POST or DELETE
112 var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
113 var oauthRequest:OAuthRequest = new OAuthRequest(method, url, null, appID, authToken);
114 var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING);
115 return String(urlStr);
118 private function sendOAuthPut(url:String, xml:XML, onComplete:Function, onError:Function, onStatus:Function):void {
120 var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "PUT"));
121 urlReq.method = "POST";
122 if (xml) { urlReq.data = xml.toXMLString(); } else { urlReq.data = true; }
123 urlReq.contentType = "application/xml";
124 urlReq.requestHeaders = new Array(new URLRequestHeader("X_HTTP_METHOD_OVERRIDE", "PUT"));
125 var loader:URLLoader = new URLLoader();
126 loader.addEventListener(Event.COMPLETE, onComplete);
127 loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
128 loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
132 private function sendOAuthGet(url:String, onComplete:Function, onError:Function, onStatus:Function):void {
133 var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "GET"));
134 urlReq.method = "GET";
135 var loader:URLLoader = new URLLoader();
136 loader.addEventListener(Event.COMPLETE, onComplete);
137 loader.addEventListener(IOErrorEvent.IO_ERROR, onError);
138 loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onStatus);
142 override public function signOAuthGet(url:String):String {
143 return signedOAuthURL(url, "GET");
146 override public function uploadChanges():void {
147 var changeset:Changeset = getActiveChangeset();
148 var upload:XML = <osmChange version="0.6"/>
149 upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode));
150 upload.appendChild(addCreated(changeset, getAllWayIDs, getWay, serialiseWay));
151 upload.appendChild(addCreated(changeset, getAllRelationIDs, getRelation, serialiseRelation));
152 upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
153 upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
154 upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
155 upload.appendChild(addDeleted(changeset, getAllRelationIDs, getRelation, serialiseEntityRoot));
156 upload.appendChild(addDeleted(changeset, getAllWayIDs, getWay, serialiseEntityRoot));
157 upload.appendChild(addDeleted(changeset, getAllNodeIDs, getNode, serialiseEntityRoot));
159 // now actually upload them
160 // make an OAuth query
162 var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
164 // build the actual request
165 var urlReq:URLRequest = new URLRequest(signedOAuthURL(url, "POST"));
166 urlReq.method = "POST";
167 urlReq.data = upload.toXMLString();
168 urlReq.contentType = "text/xml";
169 var loader:URLLoader = new URLLoader();
170 loader.addEventListener(Event.COMPLETE, diffUploadComplete);
171 loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
172 loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
175 dispatchEvent(new Event(SAVE_STARTED));
178 private function diffUploadComplete(event:Event):void {
179 // response should be XML describing the progress
180 var results:XML = new XML((URLLoader(event.target).data));
182 for each( var update:XML in results.child("*") ) {
183 var oldID:Number = Number(update.@old_id);
184 var newID:Number = Number(update.@new_id);
185 var version:uint = uint(update.@new_version);
186 var type:String = update.name();
190 if ( type == "node" ) killNode(oldID);
191 else if ( type == "way" ) killWay(oldID);
192 else if ( type == "relation" ) killRelation(oldID);
197 if ( type == "node" ) entity = getNode(oldID);
198 else if ( type == "way" ) entity = getWay(oldID);
199 else if ( type == "relation" ) entity = getRelation(oldID);
200 entity.markClean(newID, version);
202 if ( oldID != newID ) {
203 if ( type == "node" ) renumberNode(oldID, entity as Node, false);
204 else if ( type == "way" ) renumberWay(oldID, entity as Way, false);
205 else if ( type == "relation" ) renumberRelation(oldID, entity as Relation, false);
210 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
211 freshenActiveChangeset();
212 markClean(); // marks the connection clean. Pressing undo from this point on leads to unexpected results
213 MainUndoStack.getGlobalStack().breakUndo(); // so, for now, break the undo stack
216 private function diffUploadError(event:IOErrorEvent):void {
217 dispatchEvent(new MapEvent(MapEvent.ERROR, { message: "Couldn't upload data: "+httpStatus+" "+event.text } ));
218 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
221 private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
222 var create:XML = <create version="0.6"/>
223 for each( var id:Number in getIDs() ) {
224 var entity:Entity = get(id);
225 if ( id >= 0 || entity.deleted )
228 var xml:XML = serialise(entity);
229 xml.@changeset = changeset.id;
230 create.appendChild(xml);
232 return create.hasComplexContent() ? create : <!-- blank create section -->;
235 private function addDeleted(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
236 var del:XML = <delete version="0.6"/>
237 for each( var id:Number in getIDs() ) {
238 var entity:Entity = get(id);
239 // creates are already included
240 if ( id < 0 || !entity.deleted )
243 var xml:XML = serialise(entity);
244 xml.@changeset = changeset.id;
245 del.appendChild(xml);
247 return del.hasComplexContent() ? del : <!-- blank delete section -->;
250 private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
251 var modify:XML = <modify version="0.6"/>
252 for each( var id:Number in getIDs() ) {
253 var entity:Entity = get(id);
254 // creates and deletes are already included
255 if ( id < 0 || entity.deleted || !entity.isDirty )
258 var xml:XML = serialise(entity);
259 xml.@changeset = changeset.id;
260 modify.appendChild(xml);
262 return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
265 private function serialiseNode(node:Node):XML {
266 var xml:XML = serialiseEntityRoot(node); //<node/>
267 serialiseEntityTags(node, xml);
273 private function serialiseWay(way:Way):XML {
274 var xml:XML = serialiseEntityRoot(way); //<node/>
275 serialiseEntityTags(way, xml);
276 for ( var i:uint = 0; i < way.length; i++ ) {
278 nd.@ref = way.getNode(i).id;
284 private function serialiseRelation(relation:Relation):XML {
285 var xml:XML = serialiseEntityRoot(relation); //<node/>
286 serialiseEntityTags(relation, xml);
287 for ( var i:uint = 0; i < relation.length; i++ ) {
288 var relMember:RelationMember = relation.getMember(i);
289 var member:XML = <member/>
290 member.@ref = relMember.entity.id;
291 member.@type = relMember.entity.getType();
292 member.@role = relMember.role;
293 xml.appendChild(member);
298 private function serialiseEntityRoot(entity:Object):XML {
300 if (entity is Way ) { xml = <way/> }
301 else if (entity is Node ) { xml = <node/> }
302 else if (entity is Relation) { xml = <relation/> }
304 xml.@version = entity.version;
308 private function serialiseEntityTags(entity:Entity, xml:XML):void {
310 xml.@version = entity.version;
311 for each( var tag:Tag in entity.getTagArray() ) {
312 var tagXML:XML = <tag/>
314 tagXML.@v = tag.value;
315 xml.appendChild(tagXML);
319 override public function fetchUserTraces():void {
320 sendOAuthGet(Connection.apiBaseURL+"user/gpx_files",
321 tracesLoadComplete, errorOnMapLoad, mapLoadStatus); //needs error handlers
322 dispatchEvent(new Event(LOAD_STARTED)); //specific to map or reusable?
325 private function tracesLoadComplete(event:Event):void {
327 var files:XML = new XML(URLLoader(event.target).data);
328 for each(var traceData:XML in files.gpx_file) {
330 t.id = traceData.@id;
331 t.name = traceData.@name;
332 t.description = traceData.description;
334 for each(var tag:XML in traceData.tag) {
335 tags.push(String(tag));
337 t.tags = tags.join(" ");
338 t.url = Connection.apiBaseURL+"gpx/"+t.id+"/data";
341 trace("loaded gpx files");
342 dispatchEvent(new Event(LOAD_COMPLETED));
343 dispatchEvent(new Event(TRACES_LOADED));