OAuth support and the basics of diff uploading.
[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
10         public class XMLConnection extends Connection {
11
12         //public var readConnection:NetConnection;
13
14                 public function XMLConnection() {
15
16                         if (Connection.policyURL!='')
17                 Security.loadPolicyFile(Connection.policyURL);
18                 }
19                 
20                 override public function loadBbox(left:Number,right:Number,
21                                                                 top:Number,bottom:Number):void {
22             var mapVars:URLVariables = new URLVariables();
23             mapVars.bbox= left+","+bottom+","+right+","+top;
24
25             var mapRequest:URLRequest = new URLRequest(Connection.apiBaseURL+"map");
26             mapRequest.data = mapVars;
27
28             var mapLoader:URLLoader = new URLLoader();
29             mapLoader.addEventListener(Event.COMPLETE, loadedMap);
30             mapLoader.load(mapRequest);
31             dispatchEvent(new Event(LOAD_STARTED));
32                 }
33
34         private function parseTags(tagElements:XMLList):Object {
35             var tags:Object = {};
36             for each (var tagEl:XML in tagElements)
37                 tags[tagEl.@k] = tagEl.@v;
38             return tags;
39         }
40
41         private function loadedMap(event:Event):void {
42             dispatchEvent(new Event(LOAD_COMPLETED));
43
44             var map:XML = new XML(URLLoader(event.target).data);
45             var id:Number;
46             var version:uint;
47             var tags:Object;
48
49             for each(var nodeData:XML in map.node) {
50                 id = Number(nodeData.@id);
51                 version = uint(nodeData.@version);
52
53                 var node:Node = getNode(id);
54                 if ( node == null ) {
55                     var lat:Number = Number(nodeData.@lat);
56                     var lon:Number = Number(nodeData.@lon);
57                     tags = parseTags(nodeData.tag);
58                     setNode(new Node(id, version, tags, lat, lon));
59                 }
60             }
61
62             for each(var data:XML in map.way) {
63                 id = Number(data.@id);
64                 version = uint(data.@version);
65
66                 var way:Way = getWay(id);
67                 if ( way == null ) {
68                     var nodes:Array = [];
69                     for each(var nd:XML in data.nd)
70                         nodes.push(getNode(Number(nd.@ref)));
71                     tags = parseTags(data.tag);
72                     setWay(new Way(id, version, tags, nodes));
73                 }
74             }
75         }
76
77         protected var appID:OAuthConsumer;
78         protected var authToken:OAuthToken;
79         
80             override public function setAppID(id:Object):void {
81                 appID = OAuthConsumer(id);
82             }
83             
84             override public function setAuthToken(id:Object):void {
85                 authToken = OAuthToken(id);
86             }
87
88         private var httpStatus:int = 0;
89         
90         private function recordStatus(event:HTTPStatusEvent):void {
91             httpStatus = event.status;
92         }
93         
94         private var lastUploadedChangesetTags:Object;
95         
96         override public function createChangeset(tags:Object):void {
97             lastUploadedChangesetTags = tags;
98             
99                 var changesetXML:XML = <osm version="0.6"><changeset /></osm>;
100                 var changeset:XML = <changeset />;
101                 for (var tagKey:Object in tags) {
102               var tagXML:XML = <tag/>;
103               tagXML.@k = tagKey;
104               tagXML.@v = tags[tagKey];
105               changesetXML.changeset.appendChild(tagXML);
106             }        
107
108             // make an OAuth query
109             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
110             var url:String = Connection.apiBaseURL+"changeset/create";
111             var params:Object = { _method: "PUT" };
112             var oauthRequest:OAuthRequest = new OAuthRequest("POST", url, params, appID, authToken);
113             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
114
115             // build the actual request
116             var urlReq:URLRequest = new URLRequest(String(urlStr));
117             urlReq.method = "POST";
118             urlReq.data = changesetXML.toXMLString();
119             urlReq.contentType = "application/xml";
120             var loader:URLLoader = new URLLoader();
121             loader.addEventListener(Event.COMPLETE, changesetCreateComplete);
122             loader.addEventListener(IOErrorEvent.IO_ERROR, changesetCreateError);
123             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
124                 loader.load(urlReq);
125             }
126
127         private function changesetCreateComplete(event:Event):void {
128             // response should be a Number changeset id
129             var id:Number = Number(URLLoader(event.target).data);
130             
131             // which means we now have a new changeset!
132             setActiveChangeset(new Changeset(id, lastUploadedChangesetTags));
133         }
134
135         private function changesetCreateError(event:IOErrorEvent):void {
136             dispatchEvent(new Event(NEW_CHANGESET_ERROR));
137         }
138         
139         override public function uploadChanges():void {
140             var changeset:Changeset = getActiveChangeset();
141             var upload:XML = <osmChange version="0.6"/>
142             upload.appendChild(addCreated(changeset, getAllNodeIDs, getNode, serialiseNode));
143             upload.appendChild(addCreated(changeset, getAllWayIDs, getWay, serialiseWay));
144             upload.appendChild(addCreated(changeset, getAllRelationIDs, getRelation, serialiseRelation));
145             upload.appendChild(addModified(changeset, getAllNodeIDs, getNode, serialiseNode));
146             upload.appendChild(addModified(changeset, getAllWayIDs, getWay, serialiseWay));
147             upload.appendChild(addModified(changeset, getAllRelationIDs, getRelation, serialiseRelation));
148             
149             // *** TODO *** deleting items
150             
151             // now actually upload them
152             // make an OAuth query
153             var sig:IOAuthSignatureMethod = new OAuthSignatureMethod_HMAC_SHA1();
154             var url:String = Connection.apiBaseURL+"changeset/" + changeset.id + "/upload";
155             var oauthRequest:OAuthRequest = new OAuthRequest("POST", url, null, appID, authToken);
156             var urlStr:Object = oauthRequest.buildRequest(sig, OAuthRequest.RESULT_TYPE_URL_STRING)
157
158             // build the actual request
159             var urlReq:URLRequest = new URLRequest(String(urlStr));
160             urlReq.method = "POST";
161             urlReq.data = upload.toXMLString();
162             urlReq.contentType = "text/xml";
163             var loader:URLLoader = new URLLoader();
164             loader.addEventListener(Event.COMPLETE, diffUploadComplete);
165             loader.addEventListener(IOErrorEvent.IO_ERROR, diffUploadError);
166             loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, recordStatus);
167                 loader.load(urlReq);
168                 
169                 dispatchEvent(new Event(SAVE_STARTED));
170         }
171
172         private function diffUploadComplete(event:Event):void {
173             // response should be XML describing the progress
174             var results:XML = new XML((URLLoader(event.target).data));
175             
176             for each( var update:XML in results.child("*") ) {
177                 var oldID:Number = Number(update.@old_id);
178                 var newID:Number = Number(update.@new_id);
179                 var version:uint = uint(update.@new_version);
180                 var type:String = update.name();
181                 
182                 var entity:Entity;
183                 if ( type == "node" ) entity = getNode(oldID);
184                 else if ( type == "way" ) entity = getWay(oldID);
185                 else if ( type == "relation" ) entity = getRelation(oldID);
186                 entity.markClean(newID, version);
187                 
188                 // *** TODO *** handle renumbering of creates, and deleting
189             }
190
191                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, true));
192         }
193
194         private function diffUploadError(event:IOErrorEvent):void {
195             trace("error "+URLLoader(event.target).data + " "+httpStatus+ " " + event.text);
196
197                 dispatchEvent(new SaveCompleteEvent(SAVE_COMPLETED, false));
198         }
199
200         private function addCreated(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
201             var create:XML = <create version="0.6"/>
202             for each( var id:Number in getIDs() ) {
203                 if ( id >= 0 )
204                     continue;
205                     
206                 var entity:Object = get(id);
207                 var xml:XML = serialise(entity);
208                 xml.@changeset = changeset.id;
209                 create.appendChild(xml);
210             }
211             return create.hasComplexContent() ? create : <!-- blank create section -->;
212         }
213
214         private function addModified(changeset:Changeset, getIDs:Function, get:Function, serialise:Function):XML {
215             var modify:XML = <modify version="0.6"/>
216             for each( var id:Number in getIDs() ) {
217                 var entity:Entity = get(id);
218                 // creates are already included
219                 if ( id < 0 || !entity.isDirty )
220                     continue;
221                     
222                 var xml:XML = serialise(entity);
223                 xml.@changeset = changeset.id;
224                 modify.appendChild(xml);
225             }
226             return modify.hasComplexContent() ? modify : <!-- blank modify section -->;
227         }
228
229         private function serialiseNode(node:Node):XML {
230             var xml:XML = <node/>
231             serialiseEntity(node, xml);
232             xml.@lat = node.lat;
233             xml.@lon = node.lon;
234             return xml;
235         }
236
237         private function serialiseWay(way:Way):XML {
238             var xml:XML = <way/>
239             serialiseEntity(way, xml);
240             for ( var i:uint = 0; i < way.length; i++ ) {
241                 var nd:XML = <nd/>
242                 nd.@ref = way.getNode(i).id;
243                 xml.appendChild(nd);
244             }
245             return xml;
246         }
247
248         private function serialiseRelation(relation:Relation):XML {
249             var xml:XML = <relation/>
250             serialiseEntity(relation, xml);
251             for ( var i:uint = 0; i < relation.length; i++ ) {
252                 var relMember:RelationMember = relation.getMember(i);
253                 var member:XML = <member/>
254                 member.@ref = relMember.entity.id;
255                 member.@type = relMember.entity.getType();
256                 member.@role = relMember.role;
257                 xml.appendChild(member);
258             }
259             return xml;
260         }
261         
262         private function serialiseEntity(entity:Entity, xml:XML):void {
263             xml.@id = entity.id;
264             xml.@version = entity.version;
265             for each( var tag:Tag in entity.getTagArray() ) {
266               var tagXML:XML = <tag/>
267               tagXML.@k = tag.key;
268               tagXML.@v = tag.value;
269               xml.appendChild(tagXML);
270             }
271         }
272         }
273 }