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.
software/GTaskManager.java

1066 lines
45 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.
*/
/*
* GTaskManager 负责管理本地笔记数据库与 Google Tasks 之间的同步过程。
*/
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;
/*
* GTaskManager 负责管理本地笔记数据库与 Google Tasks 之间的同步过程。
*/
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;
// 用于管理任务列表、节点、元数据和 GID 与 NID 映射的哈希表
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
private GTaskManager() {
// 初始化同步状态标志,表示当前没有正在进行同步
mSyncing = false;
// 初始化取消同步标志,表示当前没有取消同步
mCancelled = false;
// 初始化用于存储 Google Task 列表的哈希表
mGTaskListHashMap = new HashMap<String, TaskList>();
// 初始化用于存储 Google Task 节点的哈希表
mGTaskHashMap = new HashMap<String, Node>();
// 初始化用于存储元数据的哈希表
mMetaHashMap = new HashMap<String, MetaData>();
// 初始化元数据列表,初始值为 null
mMetaList = null;
// 初始化用于存储本地删除的笔记 ID 的集合
mLocalDeleteIdMap = new HashSet<Long>();
// 初始化用于存储 Google Task ID 到本地笔记 ID 映射的哈希表
mGidToNid = new HashMap<String, Long>();
// 初始化用于存储本地笔记 ID 到 Google Task ID 映射的哈希表
mNidToGid = new HashMap<Long, String>();
}
/*
* 获取 GTaskManager 的单例实例。
* 该方法使用 synchronized 关键字确保线程安全,防止多个线程同时创建实例。
* @return GTaskManager 的单例实例
*/
public static synchronized GTaskManager getInstance() {
// 检查单例实例是否已经创建
if (mInstance == null) {
// 如果未创建,则创建一个新的 GTaskManager 实例
mInstance = new GTaskManager();
}
return mInstance; // 返回单例实例
}
/*
* 设置活动上下文,用于获取认证令牌。
* 该方法使用 synchronized 关键字确保线程安全,防止多个线程同时修改 mActivity 变量。
* @param activity 活动上下文
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
// 将传入的活动上下文赋值给 mActivity 变量
mActivity = activity;
}
/*
* 同步本地笔记数据库与 Google Tasks。
* 该方法负责初始化同步过程,处理同步过程中的各种异常,并返回同步结果状态。
* @param context 应用程序上下文
* @param asyncTask 异步任务对象,用于发布同步进度
* @return 同步结果状态,可能的值为 STATE_SUCCESS, STATE_NETWORK_ERROR, STATE_INTERNAL_ERROR, STATE_SYNC_IN_PROGRESS, STATE_SYNC_CANCELLED
*/
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
// 登录 Google Task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
// 从 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 Task 列表。
* 该方法从 Google Task 获取任务列表,并初始化元数据列表和任务列表。
* @throws NetworkFailureException 如果网络操作失败
*/
private void initGTaskList() throws NetworkFailureException {
// 检查是否已经取消同步
if (mCancelled)
return;
// 获取 GTaskClient 实例
GTaskClient client = GTaskClient.getInstance();
try {
// 从 Google Task 获取任务列表
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
// 首先初始化元数据列表
mMetaList = null; //初始化元数据列表,将其设置为 null。
for (int i = 0; i < jsTaskLists.length(); i++) {
//遍历任务列表,逐个处理每个任务列表。
JSONObject object = jsTaskLists.getJSONObject(i);
//获取当前任务列表的 JSONObject
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID);
//获取当前任务列表的 Google Task ID
//获取当前任务列表的名称。
String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME);
// 检查是否是元数据列表
//如果是,则初始化元数据列表并加载元数据。
if (name
.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) {
mMetaList = new TaskList();
//创建一个新的 TaskList 对象作为元数据列表。
mMetaList.setContentByRemoteJSON(object);
//使用远程 JSON 数据设置元数据列表的内容。
// load meta data
// 加载元数据
JSONArray jsMetas = client.getTaskList(gid);
//获取元数据列表中的元数据,返回一个 JSONArray。
for (int j = 0; j < jsMetas.length(); j++) {
//遍历元数据列表,逐个处理每个元数据。
object = (JSONObject) jsMetas.getJSONObject(j);
MetaData metaData = new MetaData();
//创建一个新的 MetaData 对象
metaData.setContentByRemoteJSON(object);
//使用远程 JSON 数据设置元数据的内容
if (metaData.isWorthSaving()) {
//检查元数据是否值得保存。如果值得保存,则将其添加到元数据列表中,并更新 mMetaHashMap
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
//检查元数据的 Google Task ID 是否存在。如果存在,则将其添加到 mMetaHashMap 中
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// create meta list if not existed
// 如果元数据列表不存在,则创建一个新的元数据列表
if (mMetaList == null) {
//检查元数据列表是否为空。如果为空,则创建一个新的元数据列表并将其添加到 Google Task 中。
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
//设置元数据列表的名称
GTaskClient.getInstance().createTaskList(mMetaList);
//将元数据列表添加到 Google Task 中
}
// 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);
// 检查是否是 MIUI 文件夹前缀的任务列表
if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)
&& !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META)) {
//检查当前任务列表是否是 MIUI 文件夹前缀的任务列表,并且不是元数据列表。如果是,则初始化任务列表并加载任务
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()) {
//检查任务是否值得保存。如果值得保存,则将其添加到任务列表中,并更新 mGTaskHashMap。
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");
}
}
/*
* 同步内容。
* 该方法负责同步本地数据库中的笔记与 Google Tasks 中的任务。
* @throws NetworkFailureException 如果网络操作失败
*/
private void syncContent() throws NetworkFailureException {
int syncType;
Cursor c = null;
String gid;
Node node;
// 清空本地删除的笔记 ID 集合
mLocalDeleteIdMap.clear();
// 检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
if (mCancelled) {
return;
}
// for local deleted note
// 处理本地删除的笔记
//使用 try-finally 结构确保在查询结束后关闭游标。
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);
//查询本地删除的笔记。查询条件为 type 不等于 Notes.TYPE_SYSTEM 且 parent_id 等于 Notes.ID_TRASH_FOLER。
if (c != null) {
//检查查询结果是否为空。如果不为空,则遍历查询结果。
while (c.moveToNext()) {
//遍历查询结果,逐个处理每个笔记。
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
//获取当前笔记的 Google Task ID。
node = mGTaskHashMap.get(gid);
//从 mGTaskHashMap 中获取对应的节点。
if (node != null) {
//检查节点是否存在。如果存在,则从 mGTaskHashMap 中移除该节点,并执行远程删除操作。
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
//执行远程删除操作。
}
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
//将当前笔记的本地 ID 添加到 mLocalDeleteIdMap 中。
}
} 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");
//查询数据库中存在的笔记。查询条件为 type 等于 Notes.TYPE_NOTE 且 parent_id 不等于 Notes.ID_TRASH_FOLER。
if (c != null) {
//检查查询结果是否为空。如果不为空,则遍历查询结果。
while (c.moveToNext()) {
//遍历查询结果,逐个处理每个笔记。
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
//获取当前笔记的 Google Task ID。
node = mGTaskHashMap.get(gid);
//从 mGTaskHashMap 中获取对应的节点。
if (node != null) {
//检查节点是否存在。如果存在,则从 mGTaskHashMap 中移除该节点,并更新 mGidToNid 和 mNidToGid 映射,然后获取同步操作类型。
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
//更新 Google Task ID 到本地笔记 ID 的映射。
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
//更新本地笔记 ID 到 Google Task ID 的映射。
syncType = node.getSyncAction(c);
//获取当前笔记的同步操作类型。
} else {
//如果节点不存在,则根据 Google Task ID 是否为空来确定同步操作类型。
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
// 本地添加
//检查 Google Task ID 是否为空。如果为空,则表示本地添加操作。
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();
//获取 mGTaskHashMap 的迭代器。
while (iter.hasNext()) {
//遍历 mGTaskHashMap逐个处理剩余的项目。
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");
}
//批量删除本地删除的笔记。如果删除失败,则抛出 ActionFailureException。
}
// refresh local sync id
// 刷新本地同步 ID
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
/*
* 同步文件夹。
* 该方法负责同步本地数据库中的文件夹与 Google Tasks 中的任务列表。
* @throws NetworkFailureException 如果网络操作失败
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
//检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
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);
//查询根文件夹。查询条件为 _id 等于 Notes.ID_ROOT_FOLDER。
if (c != null) {
//检查查询结果是否为空。如果不为空,则处理查询结果。
c.moveToNext();
//移动游标到下一行。
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
//获取当前文件夹的 Google Task ID。
node = mGTaskHashMap.get(gid);
//从 mGTaskHashMap 中获取对应的节点。
if (node != null) {
//检查节点是否存在。如果存在,则从 mGTaskHashMap 中移除该节点,并更新 mGidToNid 和 mNidToGid 映射。
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
//更新 Google Task ID 到本地笔记 ID 的映射。
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// for system folder, only update remote name if necessary
//更新本地笔记 ID 到 Google Task ID 的映射。
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);
//查询通话记录文件夹。查询条件为 _id 等于 Notes.ID_CALL_RECORD_FOLDER。
if (c != null) {
//检查查询结果是否为空。如果不为空,则处理查询结果。
if (c.moveToNext()) {
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
//检查节点是否存在。如果存在,则从 mGTaskHashMap 中移除该节点,并更新 mGidToNid 和 mNidToGid 映射。
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");
//查询本地存在的文件夹。查询条件为 type 等于 Notes.TYPE_FOLDER 且 parent_id 不等于 Notes.ID_TRASH_FOLER。
if (c != null) {
//检查查询结果是否为空。如果不为空,则遍历查询结果。
while (c.moveToNext()) {
//遍历查询结果,逐个处理每个文件夹。
gid = c.getString(SqlNote.GTASK_ID_COLUMN);
node = mGTaskHashMap.get(gid);
if (node != null) {
//检查节点是否存在。如果存在,则从 mGTaskHashMap 中移除该节点,并更新 mGidToNid 和 mNidToGid 映射,然后获取同步操作类型。
mGTaskHashMap.remove(gid);
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
syncType = node.getSyncAction(c);
} else {
//如果节点不存在,则根据 Google Task ID 是否为空来确定同步操作类型。
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
//检查 Google Task ID 是否为空。如果为空,则表示本地添加操作
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
//如果 Google Task ID 不为空,则表示远程删除操作。
// 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();
//获取 mGTaskListHashMap 的迭代器。
while (iter.hasNext()) {
//遍历 mGTaskListHashMap逐个处理远程添加的文件夹。
Map.Entry<String, TaskList> entry = iter.next();
//获取当前的键值对
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
//检查 mGTaskHashMap 中是否包含该节点。如果包含,则从 mGTaskHashMap 中移除该节点,并执行本地添加操作。
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled) //检查是否已经取消同步。如果没有取消,则提交更新。
GTaskClient.getInstance().commitUpdate();
}
/*
* 执行内容同步操作。
* 该方法根据同步类型执行相应的同步操作。
* @param syncType 同步类型
* @param node 节点对象
* @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:
// 本地删除操作。从 mMetaHashMap 中获取元数据。如果元数据存在,则调用 GTaskClient.getInstance().deleteNode(meta) 方法删除元数据。将本地笔记 ID 添加到 mLocalDeleteIdMap 中。
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:
// 远程删除操作。从 mMetaHashMap 中获取元数据。如果元数据存在,则调用 GTaskClient.getInstance().deleteNode(meta) 方法删除元数据。调用 GTaskClient.getInstance().deleteNode(node) 方法删除节点。
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");
}
}
/*
* 添加本地节点。
* 该方法负责将远程节点添加到本地数据库中。
* @param node 远程节点对象
* @throws NetworkFailureException 如果网络操作失败
*/
private void addLocalNode(Node node) throws NetworkFailureException {
if (mCancelled) {
//检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
return;
}
SqlNote 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 {
//其他文件夹。创建新的本地笔记,并设置内容和父节点 ID。
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
//如果是任务。创建新的本地笔记,并设置内容和父节点 ID。
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
//处理 JSON 解析异常。
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
//检查是否包含笔记信息。如果包含笔记信息,则检查笔记 ID 是否可用。
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
//检查笔记信息是否包含 ID。如果包含 ID则检查 ID 是否可用。
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one
//检查笔记 ID 是否存在于本地数据库中。如果存在,则移除笔记 ID。
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
//检查是否包含数据信息。如果包含数据信息,则检查数据 ID 是否可用。
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)) {
//检查数据信息是否包含 ID。如果包含 ID则检查 ID 是否可用。
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create
// a new one
//检查数据 ID 是否存在于本地数据库中。如果存在,则移除数据 ID。
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());
//获取父节点的本地 ID。
if (parentId == null) {
//检查父节点的本地 ID 是否存在。如果不存在,则抛出 ActionFailureException 异常。
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());//更新 Google Task ID 到本地笔记 ID 的映射。
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());//更新本地笔记 ID 到 Google Task ID 的映射。
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
updateRemoteMeta(node.getGid(), sqlNote);//更新元数据。
}
/*
* 更新本地节点。
* 该方法负责将远程节点的更新同步到本地数据库中。
* @param node 远程节点对象
* @param c 游标对象
* @throws NetworkFailureException 如果网络操作失败
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
//检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
return;
}
SqlNote sqlNote;
// update the note locally
//声明一个 SqlNote 变量,用于存储本地笔记。
sqlNote = new SqlNote(mContext, c);
//创建一个新的 SqlNote 对象,并使用游标 c 初始化。
sqlNote.setContent(node.getLocalJSONFromContent());
//设置本地笔记的内容为远程节点的本地 JSON 内容。
Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
//获取父节点的本地 ID。如果节点是任务则从 mGidToNid 映射中获取父节点的本地 ID。如果节点不是任务则将父节点 ID 设置为根文件夹的 ID。
if (parentId == null) {
//检查父节点的本地 ID 是否存在。如果不存在,则记录错误日志并抛出 ActionFailureException 异常。
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());//设置本地笔记的父节点 ID。
sqlNote.commit(true);//提交本地笔记的更新。
// update meta info
//更新元数据信息。
updateRemoteMeta(node.getGid(), sqlNote);
}
/*
* 添加远程节点。
* 该方法负责将本地笔记添加到 Google Tasks 中。
* @param node 远程节点对象
* @param c 游标对象
* @throws NetworkFailureException 如果网络操作失败
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
//检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
//创建一个新的 SqlNote 对象,并使用游标 c 初始化。
Node n;
//声明一个 Node 变量,用于存储远程节点
// update remotely
if (sqlNote.isNoteType()) {
//检查本地笔记是否是笔记类型。如果是笔记类型,则创建一个新的 Task 对象,并设置内容。
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
//检查父任务列表的 Google Task ID 是否存在。如果不存在,则记录错误日志并抛出 ActionFailureException 异常。
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;
//将 Task 对象赋值给 Node 变量。
// add meta
//添加元数据。
updateRemoteMeta(task.getGid(), sqlNote);
} else {
//如果是文件夹类型。创建一个新的 TaskList 对象,并设置内容。
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)
//检查本地笔记的 ID 是否是根文件夹的 ID。如果是根文件夹则设置文件夹名称为默认文件夹名称。
folderName += GTaskStringUtils.FOLDER_DEFAULT;
else if (sqlNote.getId() == Notes.ID_CALL_RECORD_FOLDER)
//检查本地笔记的 ID 是否是通话记录文件夹的 ID。如果是通话记录文件夹则设置文件夹名称为通话记录文件夹名称。
folderName += GTaskStringUtils.FOLDER_CALL_NOTE;
else
//其他文件夹。设置文件夹名称为本地笔记的片段。
folderName += sqlNote.getSnippet();
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
//遍历 mGTaskListHashMap查找匹配的文件夹。
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
//检查任务列表的名称是否与文件夹名称匹配。如果匹配,则将任务列表赋值给 tasklist 变量,并从 mGTaskHashMap 中移除该节点。
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
//检查 mGTaskHashMap 中是否包含该节点。如果包含,则从 mGTaskHashMap 中移除该节点。
mGTaskHashMap.remove(gid);
}
break;
}
}
// no match we can add now
if (tasklist == null) {
//如果没有匹配的文件夹,则可以添加。创建一个新的 TaskList 对象,并设置内容。
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;//将 TaskList 对象赋值给 Node 变量。
}
// update local note
sqlNote.setGtaskId(n.getGid());//设置本地笔记的 Google Task ID
sqlNote.commit(false);//提交本地笔记的更新。
sqlNote.resetLocalModified();//重置本地笔记的修改标志。
sqlNote.commit(true);//重置本地笔记的修改标志。
// gid-id mapping
mGidToNid.put(n.getGid(), sqlNote.getId());//更新 Google Task ID 到本地笔记 ID 的映射。
mNidToGid.put(sqlNote.getId(), n.getGid());//更新本地笔记 ID 到 Google Task ID 的映射。
}
/*
* 更新远程节点。
* 该方法负责将本地笔记的更新同步到 Google Tasks 中。
* @param node 远程节点对象
* @param c 游标对象
* @throws NetworkFailureException 如果网络操作失败
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
//检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);//创建一个新的 SqlNote 对象,并使用游标 c 初始化。
// update remotely
//使用本地笔记的内容更新远程节点的内容。
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);
//将更新后的节点添加到 GTaskClient 的更新列表中。
// 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) {
//检查父任务列表的 Google Task ID 是否存在。如果不存在,则记录错误日志并抛出 ActionFailureException 异常。
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);//提交本地笔记的更新。
}
/*
* 更新远程元数据。
* 该方法负责将本地笔记的元数据同步到 Google Tasks 中。
* @param gid Google Task ID
* @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。
* 该方法负责刷新本地笔记的同步 ID确保本地笔记的同步 ID 与 Google Tasks 中的最新数据一致。
* @throws NetworkFailureException 如果网络操作失败
*/
private void refreshLocalSyncId() throws NetworkFailureException {
// 检查是否已经取消同步。如果 mCancelled 为 true则直接返回不执行后续操作。
if (mCancelled) {
return;
}
// get the latest gtask list
mGTaskHashMap.clear();//清空 mGTaskHashMap
mGTaskListHashMap.clear();//清空 mGTaskListHashMap。
mMetaHashMap.clear();//清空 mMetaHashMap。
initGTaskList();//初始化 Google Task 列表。
Cursor c = null;//声明一个游标变量 c用于存储查询结果。
try {
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE,
"(type<>? AND parent_id<>?)", new String[] {
//查询本地笔记。查询条件为 type 不等于 Notes.TYPE_SYSTEM 且 parent_id 不等于 Notes.ID_TRASH_FOLER。
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 中移除该节点,并更新本地笔记的同步 ID。
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 {
//如果节点不存在,则记录错误日志并抛出 ActionFailureException 异常。
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 账户名称。
* @return 同步账户名称
*/
public String getSyncAccount() {
// 获取 GTaskClient 的单例实例,并返回同步账户的名称
return GTaskClient.getInstance().getSyncAccount().name;
}
/*
* 取消同步。
* 该方法用于设置取消同步标志,以便在同步过程中停止同步操作。
*/
public void cancelSync() {
// 设置取消同步标志为 true
mCancelled = true;
}
}