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.
xiaomi_notes_reading/src/notes/gtask/remote/GTaskManager.java

905 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;
/**
* Google任务同步管理器
* 负责本地Note与Google任务的同步逻辑
*/
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; //取消同步
private static GTaskManager mInstance = null; //单例实例
private Activity mActivity;
private Context mContext;
private ContentResolver mContentResolver;
private boolean mSyncing; //同步状态标识:是否正在同步
private boolean mCancelled; //是否已取消
private HashMap<String, TaskList> mGTaskListHashMap; //远程任务列表key任务列表GID
private HashMap<String, Node> mGTaskHashMap; //所有远程节点(任务/列表key节点GID
private HashMap<String, MetaData> mMetaHashMap; //元数据key关联的任务GID
private TaskList mMetaList; //元数据列表
private HashSet<Long> mLocalDeleteIdMap; //记录需要从远程删除的本地笔记ID
private HashMap<String, Long> mGidToNid; //远程GID到本地笔记ID的映射关系
private HashMap<Long, String> mNidToGid; //本地笔记ID到远程GID的映射关系
//初始化所有集合和状态变量(单例模式防止外部实例化)
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>();
}
// 单例获取方法:保证全局唯一实例
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
// 设置Activity上下文用于后续登录时获取认证token
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
//协调本地与远程数据同步,返回同步状态
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实例
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服务器获取任务列表和元数据
private void initGTaskList() throws NetworkFailureException {
// 若同步已取消,直接返回
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance(); // 获取网络客户端
try {
// 1. 获取所有远程任务列表JSON数组
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();
//用远程JSON初始化元数据列表
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) {
// 存储元数据key关联的任务GID
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// create meta list if not existed
// 3. 若元数据列表不存在,则创建一个
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META); // 设置名称
GTaskClient.getInstance().createTaskList(mMetaList); // 调用客户端创建列表
}
// init task list
// 4. 初始化普通任务列表(排除元数据列表)
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); // 用远程JSON初始化列表
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); // 用远程JSON初始化任务
if (task.isWorthSaving()) {
task.setMetaInfo(mMetaHashMap.get(gid)); // 关联元数据
tasklist.addChildTask(task);
mGTaskHashMap.put(gid, task); // 存入全局节点集合
}
}
}
}
} catch (JSONException e) { // 处理JSON解析异常
Log.e(TAG, e.toString());
e.printStackTrace();
throw new ActionFailureException("initGTaskList: handing JSONObject failed");
}
}
// 同步内容:协调本地与远程的任务/列表数据(核心同步逻辑)
private void syncContent() throws NetworkFailureException {
int syncType; // 同步类型(新增/删除/更新等)
Cursor c = null; // 数据库查询游标
String gid; // 远程GID
Node node; // 远程节点
mLocalDeleteIdMap.clear();
if (mCancelled) {
return;
}
// for local deleted note
// 1. 处理本地已删除的笔记(在回收站中的非系统类型笔记)
try {
// 查询条件:不是系统类型+父ID是回收站
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_FOLDER)
}, null);
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取笔记关联的远程GID
node = mGTaskHashMap.get(gid); // 从远程节点集合中查找对应节点
if (node != null) { // 若远程存在该节点,需要删除远程节点
mGTaskHashMap.remove(gid); // 从本地缓存移除
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行远程删除
}
// 记录本地已删除的笔记ID后续会从本地彻底删除
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
// 2. 优先同步文件夹(任务列表)
syncFolder();
// for note existing in database
// 3. 处理本地已存在的普通笔记(非回收站中的笔记类型)
try {
// 查询条件Note类型 + 父ID不是回收站
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_FOLDER)
}, NoteColumns.TYPE + " DESC");
if (c != null) {
while (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取关联的远程GID
node = mGTaskHashMap.get(gid);
// 远程存在对应节点
if (node != null) {
mGTaskHashMap.remove(gid); // 从本地缓存移除
// 记录GID与本地ID的映射关系
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 本地GID为空本地新增需要同步到远程
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete 本地有GID但远程无远程已删除需要删除本地
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
// 4. 处理远程存在但本地不存在的节点(需要同步到本地)
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
// 5. 清理本地已删除的笔记(若未取消同步)
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// refresh local sync id
// 6. 提交更新并刷新本地同步ID若未取消
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
// 同步文件夹(任务列表):处理本地与远程的文件夹数据
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// for root folder
// 1. 同步根文件夹
try {
// 查询根文件夹ID固定为Notes.ID_ROOT_FOLDER
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
// 2. 同步通话记录文件夹
try {
// 查询通话记录文件夹ID固定为Notes.ID_CALL_RECORD_FOLDER
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
// 3. 同步本地已存在的普通文件夹(非系统类型,非回收站)
try {
// 查询条件:文件夹 + 父ID不是回收站
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_FOLDER)
}, 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
// 本地GID为空本地新增同步到远程
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
// 本地有GID但远程无远程已删除删除本地
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
// 4. 处理远程存在但本地不存在的文件夹(同步到本地)
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();
}
// 执行具体同步操作:根据同步类型处理本地/远程数据
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)); // 记录本地ID待删除
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");
}
}
// 本地新增节点:将远程节点同步到本地数据库
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()); // 用远程数据构建本地JSON
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else { // 节点是任务(笔记)
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent(); // 用远程数据构建本地JSON
try {
// 处理笔记ID冲突若远程数据中的ID已在本地存在移除ID让本地自动生成
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);
}
}
}
// 处理数据ID冲突同理移除已存在的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);
// 获取父文件夹的本地ID通过GID映射
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()); // 设置父目录ID
}
// 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);
}
// 更新本地节点:将远程更新同步到本地
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());
// 确定父目录ID任务的父是列表列表的父是根目录
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);
}
// 远程新增节点:将本地节点同步到远程
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()); // 用本地JSON初始化远程任务
// 获取父文件夹的远程GID通过本地ID映射
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
// 更新本地节点的远程GID
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false); // 保存GID
sqlNote.resetLocalModified(); // 重置本地修改标记(避免重复同步)
sqlNote.commit(true); // 提交修改
// gid-id mapping
// 更新映射关系
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
// 更新远程节点:将本地更新同步到远程
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(); // 原父列表
// 获取当前父文件夹的远程GID通过本地ID映射
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);
}
// 更新远程元数据:同步本地笔记的元数据到远程
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将远程的最后修改时间同步到本地用于后续同步对比
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_FOLDER)
}, 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);
// 更新本地节点的同步ID远程最后修改时间
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;
}
}
}
// 获取同步账户返回当前登录的Google账户名
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
mCancelled = true;
}
}