@@ -68,38 +68,196 @@ class ParseObject extends ParseBase implements ParseCloneable {
68
68
final String body = json.encode (toJson (forApiRQ: true ));
69
69
final Response result = await _client.post (url, body: body);
70
70
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
-
78
71
return handleResponse <ParseObject >(
79
72
this , result, ParseApiRQ .create, _debug, className);
80
73
} on Exception catch (e) {
81
74
return handleException (e, ParseApiRQ .create, _debug, className);
82
75
}
83
76
}
84
77
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
+
85
90
/// Saves the current object online
86
91
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
+ }
89
99
} 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
+ }
98
198
}
199
+ } else if (! _canbeSerialized (aftersaving, value: getObjectData ())) {
200
+ return false ;
99
201
}
202
+ // TODO(yulingtianxia): handle ACL
203
+ return true ;
100
204
}
101
205
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.
103
261
ParseRelation getRelation (String key) {
104
262
return ParseRelation (parent: this , key: key);
105
263
}
@@ -115,8 +273,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
115
273
}
116
274
117
275
/// 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] );
120
278
}
121
279
122
280
/// Remove multiple elements from an array of an object
@@ -159,8 +317,11 @@ class ParseObject extends ParseBase implements ParseCloneable {
159
317
}
160
318
}
161
319
320
+ void setAddUnique (String key, dynamic value) {
321
+ _arrayOperation ('AddUnique' , key, < dynamic > [value]);
322
+ }
162
323
/// 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) {
164
325
_arrayOperation ('AddUnique' , key, values);
165
326
}
166
327
@@ -175,8 +336,8 @@ class ParseObject extends ParseBase implements ParseCloneable {
175
336
}
176
337
177
338
/// 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] );
180
341
}
181
342
182
343
void addRelation (String key, List <dynamic > values) {
@@ -208,6 +369,7 @@ class ParseObject extends ParseBase implements ParseCloneable {
208
369
209
370
/// Used in array Operations in save() method
210
371
void _arrayOperation (String arrayAction, String key, List <dynamic > values) {
372
+ // TODO(yulingtianxia): Array operations should be incremental. Merge add and remove operation.
211
373
set <Map <String , dynamic >>(
212
374
key, < String , dynamic > {'__op' : arrayAction, 'objects' : values});
213
375
}
0 commit comments