@@ -68,38 +68,196 @@ class ParseObject extends ParseBase implements ParseCloneable {
6868 final String body = json.encode (toJson (forApiRQ: true ));
6969 final Response result = await _client.post (url, body: body);
7070
71- //Set the objectId on the object after it is created.
72- //This allows you to perform operations on the object after creation
73- if (result.statusCode == 201 ) {
74- final Map <String , dynamic > map = json.decode (result.body);
75- objectId = map['objectId' ].toString ();
76- }
77-
7871 return handleResponse <ParseObject >(
7972 this , result, ParseApiRQ .create, _debug, className);
8073 } on Exception catch (e) {
8174 return handleException (e, ParseApiRQ .create, _debug, className);
8275 }
8376 }
8477
78+ Future <ParseResponse > update () async {
79+ try {
80+ final Uri url = getSanitisedUri (_client, '$_path /$objectId ' );
81+ final String body = json.encode (toJson (forApiRQ: true ));
82+ final Response result = await _client.put (url, body: body);
83+ return handleResponse <ParseObject >(
84+ this , result, ParseApiRQ .save, _debug, className);
85+ } on Exception catch (e) {
86+ return handleException (e, ParseApiRQ .save, _debug, className);
87+ }
88+ }
89+
8590 /// Saves the current object online
8691 Future <ParseResponse > save () async {
87- if (getObjectData ()[keyVarObjectId] == null ) {
88- return create ();
92+ final ParseResponse response = await _saveChildren (this );
93+ if (response.success) {
94+ if (objectId == null ) {
95+ return create ();
96+ } else {
97+ return update ();
98+ }
8999 } else {
90- try {
91- final Uri url = getSanitisedUri (_client, '$_path /$objectId ' );
92- final String body = json.encode (toJson (forApiRQ: true ));
93- final Response result = await _client.put (url, body: body);
94- return handleResponse <ParseObject >(
95- this , result, ParseApiRQ .save, _debug, className);
96- } on Exception catch (e) {
97- return handleException (e, ParseApiRQ .save, _debug, className);
100+ return response;
101+ }
102+ }
103+
104+ Future <ParseResponse > _saveChildren (dynamic object) async {
105+ final Set <ParseObject > uniqueObjects = Set <ParseObject >();
106+ final Set <ParseFile > uniqueFiles = Set <ParseFile >();
107+ if (! _collectionDirtyChildren (object, uniqueObjects, uniqueFiles,
108+ Set <ParseObject >(), Set <ParseObject >())) {
109+ final ParseResponse response = ParseResponse ();
110+ return response;
111+ }
112+ if (object is ParseObject ) {
113+ uniqueObjects.remove (object);
114+ }
115+ for (ParseFile file in uniqueFiles) {
116+ final ParseResponse response = await file.save ();
117+ if (! response.success) {
118+ return response;
119+ }
120+ }
121+ List <ParseObject > remaining = uniqueObjects.toList ();
122+ final List <ParseObject > finished = List <ParseObject >();
123+ final ParseResponse totalResponse = ParseResponse ()
124+ ..success = true
125+ ..results = List <dynamic >()
126+ ..statusCode = 200 ;
127+ while (remaining.isNotEmpty) {
128+ /* Partition the objects into two sets: those that can be save immediately,
129+ and those that rely on other objects to be created first. */
130+ final List <ParseObject > current = List <ParseObject >();
131+ final List <ParseObject > nextBatch = List <ParseObject >();
132+ for (ParseObject object in remaining) {
133+ if (object._canbeSerialized (finished)) {
134+ current.add (object);
135+ } else {
136+ nextBatch.add (object);
137+ }
138+ }
139+ remaining = nextBatch;
140+ // TODO(yulingtianxia): lazy User
141+ /* Batch requests have currently a limit of 50 packaged requests per single request
142+ This splitting will split the overall array into segments of upto 50 requests
143+ and execute them concurrently with a wrapper task for all of them. */
144+ final List <List <ParseObject >> chunks = [];
145+ for (int i = 0 ; i < current.length; i += 50 ) {
146+ chunks.add (current.sublist (i, min (current.length, i + 50 )));
147+ }
148+
149+ for (List <ParseObject > chunk in chunks) {
150+ final List <dynamic > requests = chunk.map <dynamic >((ParseObject obj) {
151+ return obj.getRequestJson (obj.objectId == null ? 'POST' : 'PUT' );
152+ }).toList ();
153+ final ParseResponse response = await batchRequest (requests, chunk);
154+ totalResponse.success & = response.success;
155+ if (response.success) {
156+ totalResponse.results.addAll (response.results);
157+ totalResponse.count += response.count;
158+ } else {
159+ // TODO(yulingtianxia): If there was an error, we want to roll forward the save changes before rethrowing.
160+ totalResponse.statusCode = response.statusCode;
161+ totalResponse.error = response.error;
162+ }
163+ }
164+ finished.addAll (current);
165+ }
166+ return totalResponse;
167+ }
168+
169+ dynamic getRequestJson (String method) {
170+ final Uri tempUri = Uri .parse (_client.data.serverUrl);
171+ final String parsePath = tempUri.path;
172+ final dynamic request = < String , dynamic > {
173+ 'method' : method,
174+ 'path' : '$parsePath $_path ' + (objectId != null ? '/$objectId ' : '' ),
175+ 'body' : toJson (forApiRQ: true )
176+ };
177+ return request;
178+ }
179+
180+ bool _canbeSerialized (List <dynamic > aftersaving, {dynamic value}) {
181+ if (value != null ) {
182+ if (value is ParseObject ) {
183+ if (value.objectId == null && ! aftersaving.contains (value)) {
184+ return false ;
185+ }
186+ } else if (value is Map ) {
187+ for (dynamic child in value.values) {
188+ if (! _canbeSerialized (aftersaving, value: child)) {
189+ return false ;
190+ }
191+ }
192+ } else if (value is List ) {
193+ for (dynamic child in value) {
194+ if (! _canbeSerialized (aftersaving, value: child)) {
195+ return false ;
196+ }
197+ }
98198 }
199+ } else if (! _canbeSerialized (aftersaving, value: getObjectData ())) {
200+ return false ;
99201 }
202+ // TODO(yulingtianxia): handle ACL
203+ return true ;
100204 }
101205
102- /// Get the instance of ParseRelation class associated with the given key.
206+ bool _collectionDirtyChildren (dynamic object, Set <ParseObject > uniqueObjects,
207+ Set <ParseFile > uniqueFiles, Set <ParseObject > seen, Set <ParseObject > seenNew) {
208+ if (object is List ) {
209+ for (dynamic child in object) {
210+ if (! _collectionDirtyChildren (
211+ child, uniqueObjects, uniqueFiles, seen, seenNew)) {
212+ return false ;
213+ }
214+ }
215+ } else if (object is Map ) {
216+ for (dynamic child in object.values) {
217+ if (! _collectionDirtyChildren (
218+ child, uniqueObjects, uniqueFiles, seen, seenNew)) {
219+ return false ;
220+ }
221+ }
222+ } else if (object is ParseACL ) {
223+ // TODO(yulingtianxia): handle ACL
224+ } else if (object is ParseFile ) {
225+ if (object.url == null ) {
226+ uniqueFiles.add (object);
227+ }
228+ } else if (object is ParseObject ) {
229+ /* Check for cycles of new objects. Any such cycle means it will be
230+ impossible to save this collection of objects, so throw an exception. */
231+ if (object.objectId != null ) {
232+ seenNew = Set <ParseObject >();
233+ } else {
234+ if (seenNew.contains (object)) {
235+ // TODO(yulingtianxia): throw an error?
236+ return false ;
237+ }
238+ seenNew.add (object);
239+ }
240+
241+ /* Check for cycles of any object. If this occurs, then there's no
242+ problem, but we shouldn't recurse any deeper, because it would be
243+ an infinite recursion. */
244+ if (seen.contains (object)) {
245+ return true ;
246+ }
247+ seen.add (object);
248+
249+ if (! _collectionDirtyChildren (
250+ object.getObjectData (), uniqueObjects, uniqueFiles, seen, seenNew)) {
251+ return false ;
252+ }
253+
254+ // TODO(yulingtianxia): Check Dirty
255+ uniqueObjects.add (object);
256+ }
257+ return true ;
258+ }
259+
260+ /// Get the instance of ParseRelation class associated with the given key.
103261 ParseRelation getRelation (String key) {
104262 return ParseRelation (parent: this , key: key);
105263 }
@@ -115,8 +273,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
115273 }
116274
117275 /// Removes an element from an Array
118- void setRemove (String key, dynamic values ) {
119- _arrayOperation ('Remove' , key, values );
276+ void setRemove (String key, dynamic value ) {
277+ _arrayOperation ('Remove' , key, < dynamic > [value] );
120278 }
121279
122280 /// Remove multiple elements from an array of an object
@@ -159,8 +317,11 @@ class ParseObject extends ParseBase implements ParseCloneable {
159317 }
160318 }
161319
320+ void setAddUnique (String key, dynamic value) {
321+ _arrayOperation ('AddUnique' , key, < dynamic > [value]);
322+ }
162323 /// Add a multiple elements to an array of an object
163- void setAddUnique (String key, List <dynamic > values) {
324+ void setAddAllUnique (String key, List <dynamic > values) {
164325 _arrayOperation ('AddUnique' , key, values);
165326 }
166327
@@ -175,8 +336,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
175336 }
176337
177338 /// Add a single element to an array of an object
178- void setAdd (String key, dynamic values ) {
179- _arrayOperation ('Add' , key, values );
339+ void setAdd (String key, dynamic value ) {
340+ _arrayOperation ('Add' , key, < dynamic > [value] );
180341 }
181342
182343 void addRelation (String key, List <dynamic > values) {
@@ -208,6 +369,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
208369
209370 /// Used in array Operations in save() method
210371 void _arrayOperation (String arrayAction, String key, List <dynamic > values) {
372+ // TODO(yulingtianxia): Array operations should be incremental. Merge add and remove operation.
211373 set <Map <String , dynamic >>(
212374 key, < String , dynamic > {'__op' : arrayAction, 'objects' : values});
213375 }
0 commit comments