blob: 9545fd7f1a74b4aa18512b349dd1dfe2b8f8e5e4 [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
Ken Shirriffe7eb9662009-06-08 12:52:50 -070066 private static final String SELECT_UNSYNCED =
Ken Shirriff36800d42009-06-15 15:24:09 -070067 "(" + _SYNC_ACCOUNT + " IS NULL OR ("
68 + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?)) and "
69 + "(" + _SYNC_ID + " IS NULL OR (" + _SYNC_DIRTY + " > 0 and "
Ken Shirriffe7eb9662009-06-08 12:52:50 -070070 + _SYNC_VERSION + " IS NOT NULL))";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
72 public AbstractTableMerger(SQLiteDatabase database,
73 String table, Uri tableURL, String deletedTable,
74 Uri deletedTableURL)
75 {
76 mDb = database;
77 mTable = table;
78 mTableURL = tableURL;
79 mDeletedTable = deletedTable;
80 mDeletedTableURL = deletedTableURL;
81 mValues = new ContentValues();
82 }
83
84 public abstract void insertRow(ContentProvider diffs,
85 Cursor diffsCursor);
86 public abstract void updateRow(long localPersonID,
87 ContentProvider diffs, Cursor diffsCursor);
88 public abstract void resolveRow(long localPersonID,
89 String syncID, ContentProvider diffs, Cursor diffsCursor);
90
91 /**
92 * This is called when it is determined that a row should be deleted from the
93 * ContentProvider. The localCursor is on a table from the local ContentProvider
94 * and its current position is of the row that should be deleted. The localCursor
95 * is only guaranteed to contain the BaseColumns.ID column so the implementation
96 * of deleteRow() must query the database directly if other columns are needed.
97 * <p>
98 * It is the responsibility of the implementation of this method to ensure that the cursor
99 * points to the next row when this method returns, either by calling Cursor.deleteRow() or
100 * Cursor.next().
101 *
102 * @param localCursor The Cursor into the local table, which points to the row that
103 * is to be deleted.
104 */
105 public void deleteRow(Cursor localCursor) {
106 localCursor.deleteRow();
107 }
108
109 /**
110 * After {@link #merge} has completed, this method is called to send
111 * notifications to {@link android.database.ContentObserver}s of changes
112 * to the containing {@link ContentProvider}. These notifications likely
113 * do not want to request a sync back to the network.
114 */
115 protected abstract void notifyChanges();
116
117 private static boolean findInCursor(Cursor cursor, int column, String id) {
118 while (!cursor.isAfterLast() && !cursor.isNull(column)) {
119 int comp = id.compareTo(cursor.getString(column));
120 if (comp > 0) {
121 cursor.moveToNext();
122 continue;
123 }
124 return comp == 0;
125 }
126 return false;
127 }
128
129 public void onMergeCancelled() {
130 mIsMergeCancelled = true;
131 }
132
133 /**
134 * Carry out a merge of the given diffs, and add the results to
135 * the given MergeResult. If we are the first merge to find
136 * client-side diffs, we'll use the given ContentProvider to
137 * construct a temporary instance to hold them.
138 */
139 public void merge(final SyncContext context,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700140 final Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 final SyncableContentProvider serverDiffs,
142 TempProviderSyncResult result,
143 SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
144 mIsMergeCancelled = false;
145 if (serverDiffs != null) {
146 if (!mDb.isDbLockedByCurrentThread()) {
147 throw new IllegalStateException("this must be called from within a DB transaction");
148 }
149 mergeServerDiffs(context, account, serverDiffs, syncResult);
150 notifyChanges();
151 }
152
153 if (result != null) {
154 findLocalChanges(result, temporaryInstanceFactory, account, syncResult);
155 }
156 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "merge complete");
157 }
158
159 /**
160 * @hide this is public for testing purposes only
161 */
162 public void mergeServerDiffs(SyncContext context,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700163 Account account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800164 boolean diffsArePartial = serverDiffs.getContainsDiffs();
165 // mark the current rows so that we can distinguish these from new
166 // inserts that occur during the merge
167 mDb.update(mTable, mSyncMarkValues, null, null);
168 if (mDeletedTable != null) {
169 mDb.update(mDeletedTable, mSyncMarkValues, null, null);
170 }
171
Fred Quintana22f71142009-03-24 20:10:17 -0700172 Cursor localCursor = null;
173 Cursor deletedCursor = null;
174 Cursor diffsCursor = null;
175 try {
176 // load the local database entries, so we can merge them with the server
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700177 final String[] accountSelectionArgs = new String[]{account.name, account.type};
Fred Quintana22f71142009-03-24 20:10:17 -0700178 localCursor = mDb.query(mTable, syncDirtyProjection,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800179 SELECT_MARKED, accountSelectionArgs, null, null,
Fred Quintana22f71142009-03-24 20:10:17 -0700180 mTable + "." + _SYNC_ID);
181 if (mDeletedTable != null) {
182 deletedCursor = mDb.query(mDeletedTable, syncIdAndVersionProjection,
183 SELECT_MARKED, accountSelectionArgs, null, null,
184 mDeletedTable + "." + _SYNC_ID);
185 } else {
186 deletedCursor =
187 mDb.rawQuery("select 'a' as _sync_id, 'b' as _sync_version limit 0", null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 }
189
Fred Quintana22f71142009-03-24 20:10:17 -0700190 // Apply updates and insertions from the server
191 diffsCursor = serverDiffs.query(mTableURL,
192 null, null, null, mTable + "." + _SYNC_ID);
193 int deletedSyncIDColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_ID);
194 int deletedSyncVersionColumn = deletedCursor.getColumnIndexOrThrow(_SYNC_VERSION);
195 int serverSyncIDColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
196 int serverSyncVersionColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_VERSION);
197 int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
198
199 String lastSyncId = null;
200 int diffsCount = 0;
201 int localCount = 0;
202 localCursor.moveToFirst();
203 deletedCursor.moveToFirst();
204 while (diffsCursor.moveToNext()) {
205 if (mIsMergeCancelled) {
206 return;
207 }
208 mDb.yieldIfContended();
209 String serverSyncId = diffsCursor.getString(serverSyncIDColumn);
210 String serverSyncVersion = diffsCursor.getString(serverSyncVersionColumn);
211 long localRowId = 0;
212 String localSyncVersion = null;
213
214 diffsCount++;
215 context.setStatusText("Processing " + diffsCount + "/"
216 + diffsCursor.getCount());
217 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "processing server entry " +
218 diffsCount + ", " + serverSyncId);
219
220 if (TRACE) {
221 if (diffsCount == 10) {
222 Debug.startMethodTracing("atmtrace");
223 }
224 if (diffsCount == 20) {
225 Debug.stopMethodTracing();
226 }
227 }
228
229 boolean conflict = false;
230 boolean update = false;
231 boolean insert = false;
232
233 if (Log.isLoggable(TAG, Log.VERBOSE)) {
234 Log.v(TAG, "found event with serverSyncID " + serverSyncId);
235 }
236 if (TextUtils.isEmpty(serverSyncId)) {
237 if (Log.isLoggable(TAG, Log.VERBOSE)) {
238 Log.e(TAG, "server entry doesn't have a serverSyncID");
239 }
240 continue;
241 }
242
243 // It is possible that the sync adapter wrote the same record multiple times,
244 // e.g. if the same record came via multiple feeds. If this happens just ignore
245 // the duplicate records.
246 if (serverSyncId.equals(lastSyncId)) {
247 if (Log.isLoggable(TAG, Log.VERBOSE)) {
248 Log.v(TAG, "skipping record with duplicate remote server id " + lastSyncId);
249 }
250 continue;
251 }
252 lastSyncId = serverSyncId;
253
254 String localSyncID = null;
255 boolean localSyncDirty = false;
256
257 while (!localCursor.isAfterLast()) {
258 if (mIsMergeCancelled) {
259 return;
260 }
261 localCount++;
262 localSyncID = localCursor.getString(2);
263
264 // If the local record doesn't have a _sync_id then
265 // it is new. Ignore it for now, we will send an insert
266 // the the server later.
267 if (TextUtils.isEmpty(localSyncID)) {
268 if (Log.isLoggable(TAG, Log.VERBOSE)) {
269 Log.v(TAG, "local record " +
270 localCursor.getLong(1) +
271 " has no _sync_id, ignoring");
272 }
273 localCursor.moveToNext();
274 localSyncID = null;
275 continue;
276 }
277
278 int comp = serverSyncId.compareTo(localSyncID);
279
280 // the local DB has a record that the server doesn't have
281 if (comp > 0) {
282 if (Log.isLoggable(TAG, Log.VERBOSE)) {
283 Log.v(TAG, "local record " +
284 localCursor.getLong(1) +
285 " has _sync_id " + localSyncID +
286 " that is < server _sync_id " + serverSyncId);
287 }
288 if (diffsArePartial) {
289 localCursor.moveToNext();
290 } else {
291 deleteRow(localCursor);
292 if (mDeletedTable != null) {
293 mDb.delete(mDeletedTable, _SYNC_ID +"=?", new String[] {localSyncID});
294 }
295 syncResult.stats.numDeletes++;
296 mDb.yieldIfContended();
297 }
298 localSyncID = null;
299 continue;
300 }
301
302 // the server has a record that the local DB doesn't have
303 if (comp < 0) {
304 if (Log.isLoggable(TAG, Log.VERBOSE)) {
305 Log.v(TAG, "local record " +
306 localCursor.getLong(1) +
307 " has _sync_id " + localSyncID +
308 " that is > server _sync_id " + serverSyncId);
309 }
310 localSyncID = null;
311 }
312
313 // the server and the local DB both have this record
314 if (comp == 0) {
315 if (Log.isLoggable(TAG, Log.VERBOSE)) {
316 Log.v(TAG, "local record " +
317 localCursor.getLong(1) +
318 " has _sync_id " + localSyncID +
319 " that matches the server _sync_id");
320 }
321 localSyncDirty = localCursor.getInt(0) != 0;
322 localRowId = localCursor.getLong(1);
323 localSyncVersion = localCursor.getString(3);
324 localCursor.moveToNext();
325 }
326
327 break;
328 }
329
330 // If this record is in the deleted table then update the server version
331 // in the deleted table, if necessary, and then ignore it here.
332 // We will send a deletion indication to the server down a
333 // little further.
334 if (findInCursor(deletedCursor, deletedSyncIDColumn, serverSyncId)) {
335 if (Log.isLoggable(TAG, Log.VERBOSE)) {
336 Log.v(TAG, "remote record " + serverSyncId + " is in the deleted table");
337 }
338 final String deletedSyncVersion = deletedCursor.getString(deletedSyncVersionColumn);
339 if (!TextUtils.equals(deletedSyncVersion, serverSyncVersion)) {
340 if (Log.isLoggable(TAG, Log.VERBOSE)) {
341 Log.v(TAG, "setting version of deleted record " + serverSyncId + " to "
342 + serverSyncVersion);
343 }
344 ContentValues values = new ContentValues();
345 values.put(_SYNC_VERSION, serverSyncVersion);
346 mDb.update(mDeletedTable, values, "_sync_id=?", new String[]{serverSyncId});
347 }
348 continue;
349 }
350
351 // If the _sync_local_id is present in the diffsCursor
352 // then this record corresponds to a local record that was just
353 // inserted into the server and the _sync_local_id is the row id
354 // of the local record. Set these fields so that the next check
355 // treats this record as an update, which will allow the
356 // merger to update the record with the server's sync id
357 if (!diffsCursor.isNull(serverSyncLocalIdColumn)) {
358 localRowId = diffsCursor.getLong(serverSyncLocalIdColumn);
359 if (Log.isLoggable(TAG, Log.VERBOSE)) {
360 Log.v(TAG, "the remote record with sync id " + serverSyncId
361 + " has a local sync id, " + localRowId);
362 }
363 localSyncID = serverSyncId;
364 localSyncDirty = false;
365 localSyncVersion = null;
366 }
367
368 if (!TextUtils.isEmpty(localSyncID)) {
369 // An existing server item has changed
Ken Shirriff26763752009-06-05 15:18:51 -0700370 // If serverSyncVersion is null, there is no edit URL;
371 // server won't let this change be written.
Ken Shirriff87d78b52009-07-20 21:30:46 -0700372 boolean recordChanged = (localSyncVersion == null) ||
373 (serverSyncVersion == null) ||
374 !serverSyncVersion.equals(localSyncVersion);
375 if (recordChanged) {
376 if (localSyncDirty) {
377 if (Log.isLoggable(TAG, Log.VERBOSE)) {
378 Log.v(TAG, "remote record " + serverSyncId
379 + " conflicts with local _sync_id " + localSyncID
380 + ", local _id " + localRowId);
Fred Quintana22f71142009-03-24 20:10:17 -0700381 }
Ken Shirriff87d78b52009-07-20 21:30:46 -0700382 conflict = true;
383 } else {
384 if (Log.isLoggable(TAG, Log.VERBOSE)) {
385 Log.v(TAG,
386 "remote record " +
387 serverSyncId +
388 " updates local _sync_id " +
389 localSyncID + ", local _id " +
390 localRowId);
391 }
392 update = true;
393 }
Ken Shirriffc1f28362009-07-20 14:57:24 -0700394 } else {
395 if (Log.isLoggable(TAG, Log.VERBOSE)) {
396 Log.v(TAG,
Ken Shirriff87d78b52009-07-20 21:30:46 -0700397 "Skipping update: localSyncVersion: " + localSyncVersion +
398 ", serverSyncVersion: " + serverSyncVersion);
Fred Quintana22f71142009-03-24 20:10:17 -0700399 }
400 }
401 } else {
402 // the local db doesn't know about this record so add it
403 if (Log.isLoggable(TAG, Log.VERBOSE)) {
404 Log.v(TAG, "remote record " + serverSyncId + " is new, inserting");
405 }
406 insert = true;
407 }
408
409 if (update) {
410 updateRow(localRowId, serverDiffs, diffsCursor);
411 syncResult.stats.numUpdates++;
412 } else if (conflict) {
413 resolveRow(localRowId, serverSyncId, serverDiffs, diffsCursor);
414 syncResult.stats.numUpdates++;
415 } else if (insert) {
416 insertRow(serverDiffs, diffsCursor);
417 syncResult.stats.numInserts++;
418 }
419 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800420
421 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fred Quintana22f71142009-03-24 20:10:17 -0700422 Log.v(TAG, "processed " + diffsCount + " server entries");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800423 }
424
Fred Quintana22f71142009-03-24 20:10:17 -0700425 // If tombstones aren't in use delete any remaining local rows that
426 // don't have corresponding server rows. Keep the rows that don't
427 // have a sync id since those were created locally and haven't been
428 // synced to the server yet.
429 if (!diffsArePartial) {
430 while (!localCursor.isAfterLast() && !TextUtils.isEmpty(localCursor.getString(2))) {
431 if (mIsMergeCancelled) {
432 return;
433 }
434 localCount++;
435 final String localSyncId = localCursor.getString(2);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800436 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fred Quintana22f71142009-03-24 20:10:17 -0700437 Log.v(TAG,
438 "deleting local record " +
439 localCursor.getLong(1) +
440 " _sync_id " + localSyncId);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800441 }
Fred Quintana22f71142009-03-24 20:10:17 -0700442 deleteRow(localCursor);
443 if (mDeletedTable != null) {
444 mDb.delete(mDeletedTable, _SYNC_ID + "=?", new String[] {localSyncId});
445 }
446 syncResult.stats.numDeletes++;
447 mDb.yieldIfContended();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800448 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 }
Fred Quintana22f71142009-03-24 20:10:17 -0700450 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "checked " + localCount +
451 " local entries");
452 } finally {
453 if (diffsCursor != null) diffsCursor.close();
454 if (localCursor != null) localCursor.close();
455 if (deletedCursor != null) deletedCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800456 }
457
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458
459 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "applying deletions from the server");
460
461 // Apply deletions from the server
462 if (mDeletedTableURL != null) {
463 diffsCursor = serverDiffs.query(mDeletedTableURL, null, null, null, null);
Fred Quintana22f71142009-03-24 20:10:17 -0700464 try {
465 while (diffsCursor.moveToNext()) {
466 if (mIsMergeCancelled) {
467 return;
468 }
469 // delete all rows that match each element in the diffsCursor
470 fullyDeleteMatchingRows(diffsCursor, account, syncResult);
471 mDb.yieldIfContended();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800472 }
Fred Quintana22f71142009-03-24 20:10:17 -0700473 } finally {
474 diffsCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 }
477 }
478
Fred Quintanad9d2f112009-04-23 13:36:27 -0700479 private void fullyDeleteMatchingRows(Cursor diffsCursor, Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 SyncResult syncResult) {
481 int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
482 final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
483
484 // delete the rows explicitly so that the delete operation can be overridden
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800485 final String[] selectionArgs;
Fred Quintana22f71142009-03-24 20:10:17 -0700486 Cursor c = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800487 try {
Fred Quintana22f71142009-03-24 20:10:17 -0700488 if (deleteBySyncId) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700489 selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn),
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700490 account.name, account.type};
Fred Quintana22f71142009-03-24 20:10:17 -0700491 c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
492 selectionArgs, null, null, null);
493 } else {
494 int serverSyncLocalIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_LOCAL_ID);
495 selectionArgs = new String[]{diffsCursor.getString(serverSyncLocalIdColumn)};
496 c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_ID, selectionArgs,
497 null, null, null);
498 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800499 c.moveToFirst();
500 while (!c.isAfterLast()) {
501 deleteRow(c); // advances the cursor
502 syncResult.stats.numDeletes++;
503 }
504 } finally {
Fred Quintana22f71142009-03-24 20:10:17 -0700505 if (c != null) c.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800506 }
507 if (deleteBySyncId && mDeletedTable != null) {
508 mDb.delete(mDeletedTable, SELECT_BY_SYNC_ID_AND_ACCOUNT, selectionArgs);
509 }
510 }
511
512 /**
513 * Converts cursor into a Map, using the correct types for the values.
514 */
515 protected void cursorRowToContentValues(Cursor cursor, ContentValues map) {
516 DatabaseUtils.cursorRowToContentValues(cursor, map);
517 }
518
519 /**
520 * Finds local changes, placing the results in the given result object.
521 * @param temporaryInstanceFactory As an optimization for the case
522 * where there are no client-side diffs, mergeResult may initially
Fred Quintanad9d2f112009-04-23 13:36:27 -0700523 * have no {@link TempProviderSyncResult#tempContentProvider}. If this is
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800524 * the first in the sequence of AbstractTableMergers to find
525 * client-side diffs, it will use the given ContentProvider to
526 * create a temporary instance and store its {@link
Fred Quintanad9d2f112009-04-23 13:36:27 -0700527 * android.content.ContentProvider} in the mergeResult.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800528 * @param account
529 * @param syncResult
530 */
531 private void findLocalChanges(TempProviderSyncResult mergeResult,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700532 SyncableContentProvider temporaryInstanceFactory, Account account,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800533 SyncResult syncResult) {
534 SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
535 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
536
Fred Quintanaffd0cb042009-08-15 21:45:26 -0700537 final String[] accountSelectionArgs = new String[]{account.name, account.type};
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800538
539 // Generate the client updates and insertions
540 // Create a cursor for dirty records
Fred Quintana22f71142009-03-24 20:10:17 -0700541 long numInsertsOrUpdates = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 Cursor localChangesCursor = mDb.query(mTable, null, SELECT_UNSYNCED, accountSelectionArgs,
543 null, null, null);
Fred Quintana22f71142009-03-24 20:10:17 -0700544 try {
545 numInsertsOrUpdates = localChangesCursor.getCount();
546 while (localChangesCursor.moveToNext()) {
547 if (mIsMergeCancelled) {
548 return;
549 }
550 if (clientDiffs == null) {
551 clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
552 }
553 mValues.clear();
554 cursorRowToContentValues(localChangesCursor, mValues);
555 mValues.remove("_id");
556 DatabaseUtils.cursorLongToContentValues(localChangesCursor, "_id", mValues,
557 _SYNC_LOCAL_ID);
558 clientDiffs.insert(mTableURL, mValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800559 }
Fred Quintana22f71142009-03-24 20:10:17 -0700560 } finally {
561 localChangesCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800562 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800563
564 // Generate the client deletions
565 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client deletions");
566 long numEntries = DatabaseUtils.queryNumEntries(mDb, mTable);
567 long numDeletedEntries = 0;
568 if (mDeletedTable != null) {
569 Cursor deletedCursor = mDb.query(mDeletedTable,
570 syncIdAndVersionProjection,
Fred Quintanad9d2f112009-04-23 13:36:27 -0700571 _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=? AND "
572 + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800573 null, null, mDeletedTable + "." + _SYNC_ID);
Fred Quintana22f71142009-03-24 20:10:17 -0700574 try {
575 numDeletedEntries = deletedCursor.getCount();
576 while (deletedCursor.moveToNext()) {
577 if (mIsMergeCancelled) {
578 return;
579 }
580 if (clientDiffs == null) {
581 clientDiffs = temporaryInstanceFactory.getTemporaryInstance();
582 }
583 mValues.clear();
584 DatabaseUtils.cursorRowToContentValues(deletedCursor, mValues);
585 clientDiffs.insert(mDeletedTableURL, mValues);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800586 }
Fred Quintana22f71142009-03-24 20:10:17 -0700587 } finally {
588 deletedCursor.close();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800589 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800590 }
591
592 if (clientDiffs != null) {
593 mergeResult.tempContentProvider = clientDiffs;
594 }
595 syncResult.stats.numDeletes += numDeletedEntries;
596 syncResult.stats.numUpdates += numInsertsOrUpdates;
597 syncResult.stats.numEntries += numEntries;
598 }
599}