blob: 8c9955a27a1fe4d61fd40e30c3dfb2d9dfc0038b [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content;
18
19import android.database.Cursor;
20import android.database.DatabaseUtils;
21import android.database.sqlite.SQLiteDatabase;
22import android.net.Uri;
23import android.os.Debug;
24import android.provider.BaseColumns;
25import static android.provider.SyncConstValue.*;
26import android.text.TextUtils;
27import android.util.Log;
Fred Quintanad9d2f112009-04-23 13:36:27 -070028import android.accounts.Account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029
30/**
31 * @hide
32 */
33public abstract class AbstractTableMerger
34{
35 private ContentValues mValues;
36
37 protected SQLiteDatabase mDb;
38 protected String mTable;
39 protected Uri mTableURL;
40 protected String mDeletedTable;
41 protected Uri mDeletedTableURL;
42 static protected ContentValues mSyncMarkValues;
43 static private boolean TRACE;
44
45 static {
46 mSyncMarkValues = new ContentValues();
47 mSyncMarkValues.put(_SYNC_MARK, 1);
48 TRACE = false;
49 }
50
51 private static final String TAG = "AbstractTableMerger";
52 private static final String[] syncDirtyProjection =
53 new String[] {_SYNC_DIRTY, BaseColumns._ID, _SYNC_ID, _SYNC_VERSION};
54 private static final String[] syncIdAndVersionProjection =
55 new String[] {_SYNC_ID, _SYNC_VERSION};
56
57 private volatile boolean mIsMergeCancelled;
58
Fred Quintanad9d2f112009-04-23 13:36:27 -070059 private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and "
60 + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
62 private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
Fred Quintanad9d2f112009-04-23 13:36:27 -070063 _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080064 private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
65
66 private static final String SELECT_UNSYNCED = ""
Fred Quintanad9d2f112009-04-23 13:36:27 -070067 + _SYNC_DIRTY + " > 0 and ((" + _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=?) "
68 + "or " + _SYNC_ACCOUNT + " is null)";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080069
70 public AbstractTableMerger(SQLiteDatabase database,
71 String table, Uri tableURL, String deletedTable,
72 Uri deletedTableURL)
73 {
74 mDb = database;
75 mTable = table;
76 mTableURL = tableURL;
77 mDeletedTable = deletedTable;
78 mDeletedTableURL = deletedTableURL;
79 mValues = new ContentValues();
80 }
81
82 public abstract void insertRow(ContentProvider diffs,
83 Cursor diffsCursor);
84 public abstract void updateRow(long localPersonID,
85 ContentProvider diffs, Cursor diffsCursor);
86 public abstract void resolveRow(long localPersonID,
87 String syncID, ContentProvider diffs, Cursor diffsCursor);
88
89 /**
90 * This is called when it is determined that a row should be deleted from the
91 * ContentProvider. The localCursor is on a table from the local ContentProvider
92 * and its current position is of the row that should be deleted. The localCursor
93 * is only guaranteed to contain the BaseColumns.ID column so the implementation
94 * of deleteRow() must query the database directly if other columns are needed.
95 * <p>
96 * It is the responsibility of the implementation of this method to ensure that the cursor
97 * points to the next row when this method returns, either by calling Cursor.deleteRow() or
98 * Cursor.next().
99 *
100 * @param localCursor The Cursor into the local table, which points to the row that
101 * is to be deleted.
102 */
103 public void deleteRow(Cursor localCursor) {
104 localCursor.deleteRow();
105 }
106
107 /**
108 * After {@link #merge} has completed, this method is called to send
109 * notifications to {@link android.database.ContentObserver}s of changes
110 * to the containing {@link ContentProvider}. These notifications likely
111 * do not want to request a sync back to the network.
112 */
113 protected abstract void notifyChanges();
114
115 private static boolean findInCursor(Cursor cursor, int column, String id) {
116 while (!cursor.isAfterLast() && !cursor.isNull(column)) {
117 int comp = id.compareTo(cursor.getString(column));
118 if (comp > 0) {
119 cursor.moveToNext();
120 continue;
121 }
122 return comp == 0;
123 }
124 return false;
125 }
126
127 public void onMergeCancelled() {
128 mIsMergeCancelled = true;
129 }
130
131 /**
132 * Carry out a merge of the given diffs, and add the results to
133 * the given MergeResult. If we are the first merge to find
134 * client-side diffs, we'll use the given ContentProvider to
135 * construct a temporary instance to hold them.
136 */
137 public void merge(final SyncContext context,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700138 final Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800139 final SyncableContentProvider serverDiffs,
140 TempProviderSyncResult result,
141 SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
142 mIsMergeCancelled = false;
143 if (serverDiffs != null) {
144 if (!mDb.isDbLockedByCurrentThread()) {
145 throw new IllegalStateException("this must be called from within a DB transaction");
146 }
147 mergeServerDiffs(context, account, serverDiffs, syncResult);
148 notifyChanges();
149 }
150
151 if (result != null) {
152 findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
153 }
154 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
155 }
156
157 /**
158 * @hide this is public for testing purposes only
159 */
160 public void mergeServerDiffs(SyncContext context,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700161 Account account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800162 boolean diffsArePartial = serverDiffs.getContainsDiffs();
163 // mark the current rows so that we can distinguish these from new
164 // inserts that occur during the merge
165 mDb.update(mTable, mSyncMarkValues, null, null);
166 if (mDeletedTable != null) {
167 mDb.update(mDeletedTable, mSyncMarkValues, null, null);
168 }
169
Fred Quintana22f71142009-03-24 20:10:17 -0700170 Cursor localCursor = null;
171 Cursor deletedCursor = null;
172 Cursor diffsCursor = null;
173 try {
174 // load the local database entries, so we can merge them with the server
Fred Quintanad9d2f112009-04-23 13:36:27 -0700175 final String[] accountSelectionArgs = new String[]{account.mName, account.mType};
Fred Quintana22f71142009-03-24 20:10:17 -0700176 localCursor = mDb.query(mTable, syncDirtyProjection,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800177 SELECT_MARKED, accountSelectionArgs, null, null,
Fred Quintana22f71142009-03-24 20:10:17 -0700178 mTable + "." + _SYNC_ID);
179 if (mDeletedTable != null) {
180 deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
181 SELECT_MARKED, accountSelectionArgs, null, null,
182 mDeletedTable + "." + _SYNC_ID);
183 } else {
184 deletedCursor =
185 mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 }
187
Fred Quintana22f71142009-03-24 20:10:17 -0700188 // Apply updates and insertions from the server
189 diffsCursor = serverDiffs.query(mTableURL,
190 null, null, null, mTable + "." + _SYNC_ID);
191 int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
192 int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
193 int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
194 int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
195 int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
196
197 String lastSyncId = null;
198 int diffsCount = 0;
199 int localCount = 0;
200 localCursor.moveToFirst();
201 deletedCursor.moveToFirst();
202 while (diffsCursor.moveToNext()) {
203 if (mIsMergeCancelled) {
204 return;
205 }
206 mDb.yieldIfContended();
207 String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
208 String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
209 long localRowId = 0;
210 String localSyncVersion = null;
211
212 diffsCount++;
213 context.setStatusText("Processing " + diffsCount + "/"
214 + diffsCursor.getCount());
215 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
216 diffsCount + ", " + serverSyncId);
217
218 if (TRACE) {
219 if (diffsCount == 10) {
220 Debug.startMethodTracing("atmtrace");
221 }
222 if (diffsCount == 20) {
223 Debug.stopMethodTracing();
224 }
225 }
226
227 boolean conflict = false;
228 boolean update = false;
229 boolean insert = false;
230
231 if (Log.isLoggable(TAG, Log.VERBOSE)) {
232 Log.v(TAG, "found event with serverSyncID " + serverSyncId);
233 }
234 if (TextUtils.isEmpty(serverSyncId)) {
235 if (Log.isLoggable(TAG, Log.VERBOSE)) {
236 Log.e(TAG, "server entry doesn't have a serverSyncID");
237 }
238 continue;
239 }
240
241 // It is possible that the sync adapter wrote the same record multiple times,
242 // e.g. if the same record came via multiple feeds. If this happens just ignore
243 // the duplicate records.
244 if (serverSyncId.equals(lastSyncId)) {
245 if (Log.isLoggable(TAG, Log.VERBOSE)) {
246 Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
247 }
248 continue;
249 }
250 lastSyncId = serverSyncId;
251
252 String localSyncID = null;
253 boolean localSyncDirty = false;
254
255 while (!localCursor.isAfterLast()) {
256 if (mIsMergeCancelled) {
257 return;
258 }
259 localCount++;
260 localSyncID = localCursor.getString(2);
261
262 // If the local record doesn't have a _sync_id then
263 // it is new. Ignore it for now, we will send an insert
264 // the the server later.
265 if (TextUtils.isEmpty(localSyncID)) {
266 if (Log.isLoggable(TAG, Log.VERBOSE)) {
267 Log.v(TAG, "local record " +
268 localCursor.getLong(1) +
269 " has no _sync_id, ignoring");
270 }
271 localCursor.moveToNext();
272 localSyncID = null;
273 continue;
274 }
275
276 int comp = serverSyncId.compareTo(localSyncID);
277
278 // the local DB has a record that the server doesn't have
279 if (comp > 0) {
280 if (Log.isLoggable(TAG, Log.VERBOSE)) {
281 Log.v(TAG, "local record " +
282 localCursor.getLong(1) +
283 " has _sync_id " + localSyncID +
284 " that is < server _sync_id " + serverSyncId);
285 }
286 if (diffsArePartial) {
287 localCursor.moveToNext();
288 } else {
289 deleteRow(localCursor);
290 if (mDeletedTable != null) {
291 mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
292 }
293 syncResult.stats.numDeletes++;
294 mDb.yieldIfContended();
295 }
296 localSyncID = null;
297 continue;
298 }
299
300 // the server has a record that the local DB doesn't have
301 if (comp < 0) {
302 if (Log.isLoggable(TAG, Log.VERBOSE)) {
303 Log.v(TAG, "local record " +
304 localCursor.getLong(1) +
305 " has _sync_id " + localSyncID +
306 " that is > server _sync_id " + serverSyncId);
307 }
308 localSyncID = null;
309 }
310
311 // the server and the local DB both have this record
312 if (comp == 0) {
313 if (Log.isLoggable(TAG, Log.VERBOSE)) {
314 Log.v(TAG, "local record " +
315 localCursor.getLong(1) +
316 " has _sync_id " + localSyncID +
317 " that matches the server _sync_id");
318 }
319 localSyncDirty = localCursor.getInt(0) != 0;
320 localRowId = localCursor.getLong(1);
321 localSyncVersion = localCursor.getString(3);
322 localCursor.moveToNext();
323 }
324
325 break;
326 }
327
328 // If this record is in the deleted table then update the server version
329 // in the deleted table, if necessary, and then ignore it here.
330 // We will send a deletion indication to the server down a
331 // little further.
332 if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
333 if (Log.isLoggable(TAG, Log.VERBOSE)) {
334 Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
335 }
336 final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
337 if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
338 if (Log.isLoggable(TAG, Log.VERBOSE)) {
339 Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
340 + serverSyncVersion);
341 }
342 ContentValues values = new ContentValues();
343 values.put(_SYNC_VERSION, serverSyncVersion);
344 mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
345 }
346 continue;
347 }
348
349 // If the _sync_local_id is present in the diffsCursor
350 // then this record corresponds to a local record that was just
351 // inserted into the server and the _sync_local_id is the row id
352 // of the local record. Set these fields so that the next check
353 // treats this record as an update, which will allow the
354 // merger to update the record with the server's sync id
355 if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
356 localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
357 if (Log.isLoggable(TAG, Log.VERBOSE)) {
358 Log.v(TAG, "the remote record with sync id " + serverSyncId
359 + " has a local sync id, " + localRowId);
360 }
361 localSyncID = serverSyncId;
362 localSyncDirty = false;
363 localSyncVersion = null;
364 }
365
366 if (!TextUtils.isEmpty(localSyncID)) {
367 // An existing server item has changed
368 boolean recordChanged = (localSyncVersion == null) ||
369 !serverSyncVersion.equals(localSyncVersion);
370 if (recordChanged) {
371 if (localSyncDirty) {
372 if (Log.isLoggable(TAG, Log.VERBOSE)) {
373 Log.v(TAG, "remote record " + serverSyncId
374 + " conflicts with local _sync_id " + localSyncID
375 + ", local _id " + localRowId);
376 }
377 conflict = true;
378 } else {
379 if (Log.isLoggable(TAG, Log.VERBOSE)) {
380 Log.v(TAG,
381 "remote record " +
382 serverSyncId +
383 " updates local _sync_id " +
384 localSyncID + ", local _id " +
385 localRowId);
386 }
387 update = true;
388 }
389 }
390 } else {
391 // the local db doesn't know about this record so add it
392 if (Log.isLoggable(TAG, Log.VERBOSE)) {
393 Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
394 }
395 insert = true;
396 }
397
398 if (update) {
399 updateRow(localRowId, serverDiffs, diffsCursor);
400 syncResult.stats.numUpdates++;
401 } else if (conflict) {
402 resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
403 syncResult.stats.numUpdates++;
404 } else if (insert) {
405 insertRow(serverDiffs, diffsCursor);
406 syncResult.stats.numInserts++;
407 }
408 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800409
410 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fred Quintana22f71142009-03-24 20:10:17 -0700411 Log.v(TAG, "processed " + diffsCount + " server entries");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800412 }
413
Fred Quintana22f71142009-03-24 20:10:17 -0700414 // If tombstones aren't in use delete any remaining local rows that
415 // don't have corresponding server rows. Keep the rows that don't
416 // have a sync id since those were created locally and haven't been
417 // synced to the server yet.
418 if (!diffsArePartial) {
419 while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
420 if (mIsMergeCancelled) {
421 return;
422 }
423 localCount++;
424 final String localSyncId = localCursor.getString(2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800425 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fred Quintana22f71142009-03-24 20:10:17 -0700426 Log.v(TAG,
427 "deleting local record " +
428 localCursor.getLong(1) +
429 " _sync_id " + localSyncId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 }
Fred Quintana22f71142009-03-24 20:10:17 -0700431 deleteRow(localCursor);
432 if (mDeletedTable != null) {
433 mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
434 }
435 syncResult.stats.numDeletes++;
436 mDb.yieldIfContended();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800437 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800438 }
Fred Quintana22f71142009-03-24 20:10:17 -0700439 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
440 " local entries");
441 } finally {
442 if (diffsCursor != null) diffsCursor.close();
443 if (localCursor != null) localCursor.close();
444 if (deletedCursor != null) deletedCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800445 }
446
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447
448 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
449
450 // Apply deletions from the server
451 if (mDeletedTableURL != null) {
452 diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
Fred Quintana22f71142009-03-24 20:10:17 -0700453 try {
454 while (diffsCursor.moveToNext()) {
455 if (mIsMergeCancelled) {
456 return;
457 }
458 // delete all rows that match each element in the diffsCursor
459 fullyDeleteMatchingRows(diffsCursor, account, syncResult);
460 mDb.yieldIfContended();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 }
Fred Quintana22f71142009-03-24 20:10:17 -0700462 } finally {
463 diffsCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800465 }
466 }
467
Fred Quintanad9d2f112009-04-23 13:36:27 -0700468 private void fullyDeleteMatchingRows(Cursor diffsCursor, Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 SyncResult syncResult) {
470 int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
471 final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
472
473 // delete the rows explicitly so that the delete operation can be overridden
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800474 final String[] selectionArgs;
Fred Quintana22f71142009-03-24 20:10:17 -0700475 Cursor c = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 try {
Fred Quintana22f71142009-03-24 20:10:17 -0700477 if (deleteBySyncId) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700478 selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn),
479 account.mName, account.mType};
Fred Quintana22f71142009-03-24 20:10:17 -0700480 c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
481 selectionArgs, null, null, null);
482 } else {
483 int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
484 selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
485 c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
486 null, null, null);
487 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800488 c.moveToFirst();
489 while (!c.isAfterLast()) {
490 deleteRow(c); // advances the cursor
491 syncResult.stats.numDeletes++;
492 }
493 } finally {
Fred Quintana22f71142009-03-24 20:10:17 -0700494 if (c != null) c.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 }
496 if (deleteBySyncId && mDeletedTable != null) {
497 mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
498 }
499 }
500
501 /**
502 * Converts cursor into a Map, using the correct types for the values.
503 */
504 protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
505 DatabaseUtils.cursorRowToContentValues(cursor, map);
506 }
507
508 /**
509 * Finds local changes, placing the results in the given result object.
510 * @param temporaryInstanceFactory As an optimization for the case
511 * where there are no client-side diffs, mergeResult may initially
Fred Quintanad9d2f112009-04-23 13:36:27 -0700512 * have no {@link TempProviderSyncResult#tempContentProvider}. If this is
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800513 * the first in the sequence of AbstractTableMergers to find
514 * client-side diffs, it will use the given ContentProvider to
515 * create a temporary instance and store its {@link
Fred Quintanad9d2f112009-04-23 13:36:27 -0700516 * android.content.ContentProvider} in the mergeResult.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800517 * @param account
518 * @param syncResult
519 */
520 private void findLocalChanges(TempProviderSyncResult mergeResult,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700521 SyncableContentProvider temporaryInstanceFactory, Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 SyncResult syncResult) {
523 SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
524 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
525
Fred Quintanad9d2f112009-04-23 13:36:27 -0700526 final String[] accountSelectionArgs = new String[]{account.mName, account.mType};
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800527
528 // Generate the client updates and insertions
529 // Create a cursor for dirty records
Fred Quintana22f71142009-03-24 20:10:17 -0700530 long numInsertsOrUpdates = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800531 Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
532 null, null, null);
Fred Quintana22f71142009-03-24 20:10:17 -0700533 try {
534 numInsertsOrUpdates = localChangesCursor.getCount();
535 while (localChangesCursor.moveToNext()) {
536 if (mIsMergeCancelled) {
537 return;
538 }
539 if (clientDiffs == null) {
540 clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
541 }
542 mValues.clear();
543 cursorRowToContentValues(localChangesCursor, mValues);
544 mValues.remove("_id");
545 DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
546 _SYNC_LOCAL_ID);
547 clientDiffs.insert(mTableURL, mValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800548 }
Fred Quintana22f71142009-03-24 20:10:17 -0700549 } finally {
550 localChangesCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800551 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552
553 // Generate the client deletions
554 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
555 long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
556 long numDeletedEntries = 0;
557 if (mDeletedTable != null) {
558 Cursor deletedCursor = mDb.query(mDeletedTable,
559 syncIdAndVersionProjection,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700560 _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=? AND "
561 + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562 null, null, mDeletedTable + "." + _SYNC_ID);
Fred Quintana22f71142009-03-24 20:10:17 -0700563 try {
564 numDeletedEntries = deletedCursor.getCount();
565 while (deletedCursor.moveToNext()) {
566 if (mIsMergeCancelled) {
567 return;
568 }
569 if (clientDiffs == null) {
570 clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
571 }
572 mValues.clear();
573 DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
574 clientDiffs.insert(mDeletedTableURL, mValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800575 }
Fred Quintana22f71142009-03-24 20:10:17 -0700576 } finally {
577 deletedCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800578 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800579 }
580
581 if (clientDiffs != null) {
582 mergeResult.tempContentProvider = clientDiffs;
583 }
584 syncResult.stats.numDeletes += numDeletedEntries;
585 syncResult.stats.numUpdates += numInsertsOrUpdates;
586 syncResult.stats.numEntries += numEntries;
587 }
588}