You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git.text/src/gtask/remote/GTaskManager.java

1060 lines
37 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.gtask.remote;
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.gtask.data.MetaData;
import net.micode.notes.gtask.data.Node;
import net.micode.notes.gtask.data.SqlNote;
import net.micode.notes.gtask.data.Task;
import net.micode.notes.gtask.data.TaskList;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.gtask.exception.NetworkFailureException;
import net.micode.notes.tool.DataUtils;
import net.micode.notes.tool.GTaskStringUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
/**
* <p>Google任务同步管理器类。</p>
* <p>设计意图作为Google任务与本地便签之间的同步协调中心管理同步流程和数据转换。</p>
* <p>核心职责:</p>
* <ul>
* <li>管理Google任务的登录认证</li>
* <li>初始化和维护Google任务列表</li>
* <li>协调本地便签与Google任务之间的内容同步</li>
* <li>处理同步冲突和异常情况</li>
* <li>提供同步状态管理和取消机制</li>
* </ul>
* <p>与其他类的关键关联:</p>
* <ul>
* <li>依赖{@link GTaskClient}执行与Google任务API的实际通信</li>
* <li>使用{@link SqlNote}进行本地便签数据的CRUD操作</li>
* <li>管理{@link TaskList}和{@link Task}对象的同步状态</li>
* <li>通过{@link MetaData}存储扩展的任务元数据</li>
* </ul>
*/
public class GTaskManager {
private static final String TAG = GTaskManager.class.getSimpleName();
/**
* 同步状态:成功
*/
public static final int STATE_SUCCESS = 0;
/**
* 同步状态:网络错误
*/
public static final int STATE_NETWORK_ERROR = 1;
/**
* 同步状态:内部错误
*/
public static final int STATE_INTERNAL_ERROR = 2;
/**
* 同步状态:同步进行中
*/
public static final int STATE_SYNC_IN_PROGRESS = 3;
/**
* 同步状态:同步已取消
*/
public static final int STATE_SYNC_CANCELLED = 4;
/**
* GTaskManager的单例实例
*/
private static GTaskManager mInstance = null;
/**
* 用于获取Google认证令牌的Activity上下文
*/
private Activity mActivity;
/**
* 应用上下文
*/
private Context mContext;
/**
* 内容解析器,用于访问本地便签数据库
*/
private ContentResolver mContentResolver;
/**
* 同步状态标志:是否正在同步
*/
private boolean mSyncing;
/**
* 同步取消标志:是否已取消同步
*/
private boolean mCancelled;
/**
* Google任务列表映射表key为任务列表的GID
*/
private HashMap<String, TaskList> mGTaskListHashMap;
/**
* Google任务节点映射表key为节点的GID
*/
private HashMap<String, Node> mGTaskHashMap;
/**
* 元数据映射表key为关联任务的GID
*/
private HashMap<String, MetaData> mMetaHashMap;
/**
* 存储元数据的任务列表
*/
private TaskList mMetaList;
/**
* 本地已删除便签的ID集合
*/
private HashSet<Long> mLocalDeleteIdMap;
/**
* GID到本地ID的映射表
*/
private HashMap<String, Long> mGidToNid;
/**
* 本地ID到GID的映射表
*/
private HashMap<Long, String> mNidToGid;
/**
* 私有构造函数,初始化同步管理器的内部状态和数据结构。
*/
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
/**
* 获取GTaskManager的单例实例。
*
* @return GTaskManager的单例实例
*/
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
/**
* 设置用于获取Google认证令牌的Activity上下文。
*
* @param activity 用于获取认证令牌的Activity
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
/**
* 执行Google任务与本地便签的同步操作。
*
* <p>业务逻辑:</p>
* <ul>
* <li>检查是否已有同步任务在进行中</li>
* <li>初始化同步上下文和数据结构</li>
* <li>登录Google任务服务</li>
* <li>初始化Google任务列表</li>
* <li>执行内容同步工作</li>
* <li>处理同步过程中的异常情况</li>
* <li>清理资源并返回同步结果状态</li>
* </ul>
*
* @param context 应用上下文
* @param asyncTask 异步任务实例,用于发布同步进度
* @return 同步结果状态:
* <ul>
* <li>{@link #STATE_SUCCESS}:同步成功</li>
* <li>{@link #STATE_NETWORK_ERROR}:网络错误</li>
* <li>{@link #STATE_INTERNAL_ERROR}:内部错误</li>
* <li>{@link #STATE_SYNC_IN_PROGRESS}:已有同步任务在进行中</li>
* <li>{@link #STATE_SYNC_CANCELLED}:同步已取消</li>
* </ul>
*/
public int sync(Context context, GTaskASyncTask asyncTask) {
if (mSyncing) {
Log.d(TAG, "Sync is in progress");
return STATE_SYNC_IN_PROGRESS;
}
mContext = context;
mContentResolver = mContext.getContentResolver();
mSyncing = true;
mCancelled = false;
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
try {
GTaskClient client = GTaskClient.getInstance();
client.resetUpdateArray();
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList();
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) {
Log.e(TAG, e.toString());
return STATE_NETWORK_ERROR;
} catch (ActionFailureException e) {
Log.e(TAG, e.toString());
return STATE_INTERNAL_ERROR;
} catch (Exception e) {
Log.e(TAG, e.toString());
e.printStackTrace();
return STATE_INTERNAL_ERROR;
} finally {
mGTaskListHashMap.clear();
mGTaskHashMap.clear();
mMetaHashMap.clear();
mLocalDeleteIdMap.clear();
mGidToNid.clear();
mNidToGid.clear();
mSyncing = false;
}
return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS;
}
/**
* 初始化Google任务列表从Google服务器获取任务列表和任务数据。
*
* <p>业务逻辑:</p>
* <ul>
* <li>获取所有任务列表</li>
* <li>初始化元数据列表(如果不存在则创建)</li>
* <li>加载所有任务列表及其包含的任务</li>
* <li>将任务列表和任务数据存储到内部映射表中</li>
* </ul>
*
* @throws NetworkFailureException 如果网络请求失败
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
mMetaList = null;
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
mMetaList.setContentByRemoteJSON(object);
// load meta data
JSONArray jsMetas = client.getTaskList(gid);
for (int j = 0; j < jsMetas.length(); j++) {
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
metaData.setContentByRemoteJSON(object);
if (metaData.isWorthSaving()) {
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// create meta list if not existed
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// init task list
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
TaskList tasklist = new TaskList();
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// load tasks
JSONArray jsTasks = client.getTaskList(gid);
for (int j = 0; j < jsTasks.length(); j++) {
object = (JSONObject) jsTasks.getJSONObject(j);
gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
Task task = new Task();
task.setContentByRemoteJSON(object);
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid));
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task);
}
}
}
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
/**
* 执行内容同步工作协调本地便签与Google任务之间的数据同步。
*
* <p>业务逻辑:</p>
* <ul>
* <li>处理本地已删除的便签</li>
* <li>优先同步文件夹</li>
* <li>同步本地存在的便签</li>
* <li>同步Google服务器上新增的任务</li>
* <li>清理本地已删除的便签</li>
* <li>提交更新并刷新本地同步ID</li>
* </ul>
*
* @throws NetworkFailureException 如果网络请求失败
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// for local deleted note
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id=?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
Log.w(TAG, "failed to query trash folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// sync folder first
syncFolder();
// for note existing in database
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_NOTE), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing note in database");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// mCancelled can be set by another thread, so we neet to check one by
// one
// clear local delete table
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// refresh local sync id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
/**
* 同步文件夹(任务列表)数据。
*
* <p>业务逻辑:</p>
* <ul>
* <li>同步根文件夹</li>
* <li>同步通话记录文件夹</li>
* <li>同步本地存在的文件夹</li>
* <li>同步Google服务器上新增的文件夹</li>
* </ul>
*
* @throws NetworkFailureException 如果网络请求失败
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// for root folder
try {
c = mContentResolver.query(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
Notes.ID_ROOT_FOLDER), SqlNote.PROJECTION_NOTE, null, null, null);
if (c != null) {
c.moveToNext();
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
Log.w(TAG, "failed to query root folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for call-note folder
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, "(_id=?)",
new String[] {
String.valueOf(Notes.ID_CALL_RECORD_FOLDER)
}, null);
if (c != null) {
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_CALL_RECORD_FOLDER);
mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid);
// for system folder, only update remote name if
// necessary
if (!node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_CALL_NOTE))
doContentSync(Node.SYNC_ACTION_UPDATE_REMOTE, node, c);
} else {
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
}
} else {
Log.w(TAG, "failed to query call note folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for local existing folders
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type=? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
doContentSync(syncType, node, c);
}
} else {
Log.w(TAG, "failed to query existing folder");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
// for remote add folders
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
/**
* 根据同步类型执行具体的内容同步操作。
*
* <p>业务逻辑:</p>
* <ul>
* <li>根据同步类型执行不同的同步操作</li>
* <li>支持添加本地节点、添加远程节点、删除本地节点、删除远程节点、更新本地节点、更新远程节点等操作</li>
* <li>处理同步冲突情况</li>
* </ul>
*
* @param syncType 同步类型
* @param node Google任务节点
* @param c 本地便签的游标
* @throws NetworkFailureException 如果网络请求失败
*/
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
MetaData meta;
switch (syncType) {
case Node.SYNC_ACTION_ADD_LOCAL:
addLocalNode(node);
break;
case Node.SYNC_ACTION_ADD_REMOTE:
addRemoteNode(node, c);
break;
case Node.SYNC_ACTION_DEL_LOCAL:
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
case Node.SYNC_ACTION_DEL_REMOTE:
meta = mMetaHashMap.get(node.getGid());
if (meta != null) {
GTaskClient.getInstance().deleteNode(meta);
}
GTaskClient.getInstance().deleteNode(node);
break;
case Node.SYNC_ACTION_UPDATE_LOCAL:
updateLocalNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_REMOTE:
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// merging both modifications maybe a good idea
// right now just use local update simply
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
/**
* 将Google任务节点添加到本地便签数据库。
*
* <p>业务逻辑:</p>
* <ul>
* <li>根据节点类型创建对应的SqlNote对象</li>
* <li>处理JSON数据中的ID冲突</li>
* <li>设置节点的父ID</li>
* <li>创建本地节点并更新映射表</li>
* <li>更新元数据</li>
* </ul>
*
* @param node 要添加的Google任务节点
* @throws NetworkFailureException 如果网络请求失败
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
if (node instanceof TaskList) {
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create
// a new one
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId.longValue());
}
// create the local node
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
* 更新本地便签数据库中的节点数据。
*
* <p>业务逻辑:</p>
* <ul>
* <li>创建SqlNote对象并更新内容</li>
* <li>设置节点的父ID</li>
* <li>提交更新并更新映射表</li>
* <li>更新元数据</li>
* </ul>
*
* @param node 要更新的Google任务节点
* @param c 本地便签的游标
* @throws NetworkFailureException 如果网络请求失败
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// update the note locally
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// update meta info
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
* 将本地便签添加到Google服务器。
*
* <p>业务逻辑:</p>
* <ul>
* <li>根据本地便签类型创建对应的Google任务或任务列表</li>
* <li>处理已存在的文件夹</li>
* <li>创建远程节点并更新本地便签的GID</li>
* <li>更新映射表</li>
* </ul>
*
* @param node Google任务节点
* @param c 本地便签的游标
* @throws NetworkFailureException 如果网络请求失败
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// update remotely
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
mGTaskListHashMap.get(parentGid).addChildTask(task);
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// add meta
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// we need to skip folder if it has already existed
String folderName = GTaskStringUtils.MIUI_FOLDER_PREFFIX;
if (sqlNote.getId() == Notes.ID_ROOT_FOLDER)
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
folderName += sqlNote.getSnippet();
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// no match we can add now
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id mapping
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
* 更新Google服务器上的节点数据。
*
* <p>业务逻辑:</p>
* <ul>
* <li>更新远程节点的内容</li>
* <li>更新元数据</li>
* <li>处理任务移动情况</li>
* <li>清除本地修改标志</li>
* </ul>
*
* @param node 要更新的Google任务节点
* @param c 本地便签的游标
* @throws NetworkFailureException 如果网络请求失败
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// update remotely
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// clear local modified flag
sqlNote.resetLocalModified();
sqlNote.commit(true);
}
/**
* 更新远程服务器上的元数据。
*
* <p>业务逻辑:</p>
* <ul>
* <li>检查元数据是否已存在</li>
* <li>如果已存在则更新,否则创建新的元数据</li>
* </ul>
*
* @param gid 任务的GID
* @param sqlNote 本地便签对象
* @throws NetworkFailureException 如果网络请求失败
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**
* 刷新本地便签的同步ID。
*
* <p>业务逻辑:</p>
* <ul>
* <li>重新初始化Google任务列表</li>
* <li>更新本地便签的同步ID</li>
* </ul>
*
* @throws NetworkFailureException 如果网络请求失败
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// get the latest gtask list
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
initGTaskList();
Cursor c = null;
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
Node node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues();
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
}
/**
* 获取当前同步账户的名称。
*
* @return 当前同步账户的名称
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
* 取消正在进行的同步操作。
*/
public void cancelSync() {
mCancelled = true;
}
}