From 0df63b47f20398c5729fdbf3d739b113c7c41e23 Mon Sep 17 00:00:00 2001 From: LuLanjian0916 <3051014030@qq.com> Date: Tue, 10 Jun 2025 13:17:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../notes/gtask/remote/GTaskManager.java | 607 ++++++++++-------- 1 file changed, 337 insertions(+), 270 deletions(-) diff --git a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java index d2b4082..d50cf72 100644 --- a/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java +++ b/MiNotes-master/app/src/main/java/net/micode/notes/gtask/remote/GTaskManager.java @@ -47,58 +47,55 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Map; - +/** + * Google Tasks同步管理类 + * 负责协调本地数据库与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; - - private HashMap mGTaskListHashMap; - - private HashMap mGTaskHashMap; - - private HashMap mMetaHashMap; - - private TaskList mMetaList; - - private HashSet mLocalDeleteIdMap; - - private HashMap mGidToNid; - - private HashMap mNidToGid; - + // 同步状态码 + 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 mGTaskListHashMap; // 远程任务列表缓存(GID为键) + private HashMap mGTaskHashMap; // 远程节点缓存(任务/列表,GID为键) + private HashMap mMetaHashMap; // 元数据缓存(关联任务与本地笔记) + private TaskList mMetaList; // 元数据存储的特殊任务列表 + private HashSet mLocalDeleteIdMap; // 本地已删除笔记ID集合 + private HashMap mGidToNid; // GID到本地笔记ID的映射 + private HashMap mNidToGid; // 本地笔记ID到GID的映射 + + /** + * 构造方法(私有化,保证单例) + */ private GTaskManager() { mSyncing = false; mCancelled = false; - mGTaskListHashMap = new HashMap(); - mGTaskHashMap = new HashMap(); - mMetaHashMap = new HashMap(); + // 初始化缓存集合 + mGTaskListHashMap = new HashMap<>(); + mGTaskHashMap = new HashMap<>(); + mMetaHashMap = new HashMap<>(); mMetaList = null; - mLocalDeleteIdMap = new HashSet(); - mGidToNid = new HashMap(); - mNidToGid = new HashMap(); + mLocalDeleteIdMap = new HashSet<>(); + mGidToNid = new HashMap<>(); + mNidToGid = new HashMap<>(); } + /** + * 获取单例实例 + * @return GTaskManager实例 + */ public static synchronized GTaskManager getInstance() { if (mInstance == null) { mInstance = new GTaskManager(); @@ -106,20 +103,31 @@ public class GTaskManager { return mInstance; } + /** + * 设置活动上下文(用于获取账号令牌) + * @param activity 活动对象 + */ public synchronized void setActivityContext(Activity activity) { - // used for getting authtoken mActivity = activity; } + /** + * 执行同步操作 + * @param context 应用上下文 + * @param asyncTask 异步任务对象(用于进度通知) + * @return 同步状态码 + */ public int sync(Context context, GTaskASyncTask asyncTask) { - if (mSyncing) { - Log.d(TAG, "Sync is in progress"); + if (mSyncing) { // 防止并发同步 + Log.d(TAG, "同步正在进行中"); return STATE_SYNC_IN_PROGRESS; } + // 初始化上下文和解析器 mContext = context; mContentResolver = mContext.getContentResolver(); mSyncing = true; mCancelled = false; + // 清空缓存 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -129,33 +137,33 @@ public class GTaskManager { try { GTaskClient client = GTaskClient.getInstance(); - client.resetUpdateArray(); + client.resetUpdateArray(); // 重置批量更新队列 - // login google task - if (!mCancelled) { - if (!client.login(mActivity)) { - throw new NetworkFailureException("login google task failed"); - } + // 登录Google Tasks + if (!mCancelled && !client.login(mActivity)) { + throw new NetworkFailureException("Google Tasks登录失败"); } - // 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()); + + } catch (NetworkFailureException e) { // 网络异常 + Log.e(TAG, "网络错误: " + e.getMessage()); return STATE_NETWORK_ERROR; - } catch (ActionFailureException e) { - Log.e(TAG, e.toString()); + } catch (ActionFailureException e) { // 操作失败 + Log.e(TAG, "操作失败: " + e.getMessage()); return STATE_INTERNAL_ERROR; - } catch (Exception e) { - Log.e(TAG, e.toString()); + } catch (Exception e) { // 其他异常 + Log.e(TAG, "未知异常: " + e.getMessage()); e.printStackTrace(); return STATE_INTERNAL_ERROR; } finally { + // 清理资源 mGTaskListHashMap.clear(); mGTaskHashMap.clear(); mMetaHashMap.clear(); @@ -165,119 +173,124 @@ public class GTaskManager { mSyncing = false; } - return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; + return mCancelled ? STATE_SYNC_CANCELLED : STATE_SUCCESS; // 返回同步结果 } + /** + * 初始化远程任务列表(从Google Tasks获取) + * @throws NetworkFailureException 网络异常 + */ private void initGTaskList() throws NetworkFailureException { - if (mCancelled) - return; + if (mCancelled) return; // 取消同步时跳过 GTaskClient client = GTaskClient.getInstance(); try { - JSONArray jsTaskLists = client.getTaskLists(); + 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)) { + // 识别元数据列表(名称以特定前缀开头) + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { mMetaList = new TaskList(); - mMetaList.setContentByRemoteJSON(object); + mMetaList.setContentByRemoteJSON(object); // 从JSON初始化列表信息 - // load meta data + // 加载元数据任务(存储在元数据列表中的任务) JSONArray jsMetas = client.getTaskList(gid); for (int j = 0; j < jsMetas.length(); j++) { - object = (JSONObject) jsMetas.getJSONObject(j); + JSONObject metaObject = 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); + metaData.setContentByRemoteJSON(metaObject); + if (metaData.isWorthSaving()) { // 过滤无效元数据 + mMetaList.addChildTask(metaData); // 添加到元数据列表 + if (metaData.getRelatedGid() != 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); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); + client.createTaskList(mMetaList); // 调用远程API创建列表 } - // 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)) { + // 过滤系统生成的文件夹(以特定前缀开头,排除元数据列表) + 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); + 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); + JSONObject taskObject = jsTasks.getJSONObject(j); + String taskGid = taskObject.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); + task.setContentByRemoteJSON(taskObject); // 从JSON初始化任务信息 + if (task.isWorthSaving()) { // 过滤无效任务 + task.setMetaInfo(mMetaHashMap.get(taskGid)); // 关联元数据 + tasklist.addChildTask(task); // 添加到任务列表 + mGTaskHashMap.put(taskGid, task); // 缓存任务节点 } } } } } catch (JSONException e) { - Log.e(TAG, e.toString()); - e.printStackTrace(); - throw new ActionFailureException("initGTaskList: handing JSONObject failed"); + Log.e(TAG, "解析任务列表JSON失败: " + e.getMessage()); + throw new ActionFailureException("初始化任务列表失败"); } } + /** + * 执行内容同步(本地与远程数据双向同步) + * @throws NetworkFailureException 网络异常 + */ private void syncContent() throws NetworkFailureException { - int syncType; - Cursor c = null; - String gid; - Node node; + int syncType; // 同步操作类型(增/删/改) + Cursor c = null; // 数据库查询游标 + String gid; // 远程GID + Node node; // 当前处理的节点 - mLocalDeleteIdMap.clear(); + 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); + "(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); + 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); + mGTaskHashMap.remove(gid); // 从缓存中移除 + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 删除远程节点 } - - mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 记录本地删除ID } } else { - Log.w(TAG, "failed to query trash folder"); + Log.w(TAG, "查询回收站失败"); } } finally { if (c != null) { @@ -286,39 +299,37 @@ public class GTaskManager { } } - // 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=? 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); + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取远程GID + node = mGTaskHashMap.get(gid); // 查找对应的远程节点 if (node != null) { - mGTaskHashMap.remove(gid); - mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mGTaskHashMap.remove(gid); // 从缓存中移除 + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 记录GID与本地ID映射 mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); + syncType = node.getSyncAction(c); // 确定同步操作类型 } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; + syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地新增,需同步到远程 } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; + syncType = Node.SYNC_ACTION_DEL_LOCAL; // 远程已删除,需本地删除 } } - doContentSync(syncType, node, c); + doContentSync(syncType, node, c); // 执行同步操作 } } else { - Log.w(TAG, "failed to query existing note in database"); + Log.w(TAG, "查询本地笔记失败"); } - } finally { if (c != null) { c.close(); @@ -326,31 +337,33 @@ public class GTaskManager { } } - // go through remaining items + // 处理远程新增但本地没有的节点(遍历剩余缓存节点) Iterator> iter = mGTaskHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); node = entry.getValue(); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + 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"); + throw new ActionFailureException("批量删除本地已删除笔记失败"); } } - // refresh local sync id + // 提交批量更新并刷新本地同步ID if (!mCancelled) { - GTaskClient.getInstance().commitUpdate(); - refreshLocalSyncId(); + GTaskClient.getInstance().commitUpdate(); // 提交所有批量操作 + refreshLocalSyncId(); // 更新本地笔记的同步时间 } - } + // ==================== 文件夹同步 ==================== + /** + * 同步文件夹(系统文件夹和用户创建的文件夹) + * @throws NetworkFailureException 网络异常 + */ private void syncFolder() throws NetworkFailureException { Cursor c = null; String gid; @@ -361,27 +374,26 @@ public class GTaskManager { 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(); + 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)) + // 仅当远程名称不一致时更新(系统文件夹名称固定) + 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); + doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c); // 新增到远程 } } else { - Log.w(TAG, "failed to query root folder"); + Log.w(TAG, "查询根文件夹失败"); } } finally { if (c != null) { @@ -390,32 +402,27 @@ public class GTaskManager { } } - // 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); + new String[]{String.valueOf(Notes.ID_CALL_RECORD_FOLDER)}, 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_CALL_RECORD_FOLDER); + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, + mNidToGid.put((long) Notes.ID_CALL_RECORD_FOLDER, gid); + // 仅当远程名称不一致时更新(系统文件夹名称固定) + 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"); + Log.w(TAG, "查询通话记录文件夹失败"); } } finally { if (c != null) { @@ -424,12 +431,12 @@ public class GTaskManager { } } - // 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=? 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); @@ -438,20 +445,18 @@ public class GTaskManager { mGTaskHashMap.remove(gid); mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); - syncType = node.getSyncAction(c); + syncType = node.getSyncAction(c); // 确定同步操作类型 } else { if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { - // local add - syncType = Node.SYNC_ACTION_ADD_REMOTE; + syncType = Node.SYNC_ACTION_ADD_REMOTE; // 本地新增,需同步到远程 } else { - // remote delete - syncType = Node.SYNC_ACTION_DEL_LOCAL; + syncType = Node.SYNC_ACTION_DEL_LOCAL; // 远程已删除,需本地删除 } } - doContentSync(syncType, node, c); + doContentSync(syncType, node, c); // 执行同步操作 } } else { - Log.w(TAG, "failed to query existing folder"); + Log.w(TAG, "查询本地文件夹失败"); } } finally { if (c != null) { @@ -460,7 +465,7 @@ public class GTaskManager { } } - // for remote add folders + // 处理远程新增但本地没有的文件夹 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -468,14 +473,23 @@ public class GTaskManager { node = entry.getValue(); if (mGTaskHashMap.containsKey(gid)) { mGTaskHashMap.remove(gid); - doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); // 新增到本地 } } + // 提交批量更新(如果有) if (!mCancelled) GTaskClient.getInstance().commitUpdate(); } + // ==================== 内容同步核心方法 ==================== + /** + * 执行具体的同步操作(根据同步类型处理节点) + * @param syncType 同步操作类型(增/删/改等) + * @param node 要同步的节点 + * @param c 本地数据库游标(可能为null) + * @throws NetworkFailureException 网络异常 + */ private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; @@ -484,44 +498,52 @@ public class GTaskManager { MetaData meta; switch (syncType) { case Node.SYNC_ACTION_ADD_LOCAL: - addLocalNode(node); + addLocalNode(node); // 远程新增,添加到本地 break; case Node.SYNC_ACTION_ADD_REMOTE: - addRemoteNode(node, c); + 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)); + 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); + GTaskClient.getInstance().deleteNode(node); // 删除远程节点 break; case Node.SYNC_ACTION_UPDATE_LOCAL: - updateLocalNode(node, c); + updateLocalNode(node, c); // 远程更新,更新本地 break; case Node.SYNC_ACTION_UPDATE_REMOTE: - updateRemoteNode(node, c); + 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"); + throw new ActionFailureException("未知同步操作类型"); } } + /** + * 将远程节点添加到本地数据库 + * @param node 远程节点(任务或列表) + * @throws NetworkFailureException 网络异常 + */ private void addLocalNode(Node node) throws NetworkFailureException { if (mCancelled) { return; @@ -529,32 +551,35 @@ public class GTaskManager { 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); + // 处理文件夹 + 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); + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置内容 + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 设置父文件夹为根文件夹 } } else { + // 处理任务(笔记) sqlNote = new SqlNote(mContext); - JSONObject js = node.getLocalJSONFromContent(); + 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 + // ID已存在,移除ID以创建新笔记 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++) { @@ -562,92 +587,106 @@ public class GTaskManager { 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 + // ID已存在,移除ID以创建新数据项 data.remove(DataColumns.ID); } } } - } } catch (JSONException e) { - Log.w(TAG, e.toString()); + Log.w(TAG, "处理JSON数据失败: " + e.getMessage()); e.printStackTrace(); } - sqlNote.setContent(js); + sqlNote.setContent(js); // 设置处理后的内容 + // 设置父文件夹(根据远程任务的父列表GID查找本地对应ID) 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"); + Log.e(TAG, "无法找到任务的父文件夹ID"); + throw new ActionFailureException("无法添加本地节点"); } sqlNote.setParentId(parentId.longValue()); } - // create the local node - sqlNote.setGtaskId(node.getGid()); - sqlNote.commit(false); + // 创建本地节点 + sqlNote.setGtaskId(node.getGid()); // 设置远程GID + sqlNote.commit(false); // 提交到数据库(不更新同步状态) - // update gid-nid mapping + // 更新GID与本地ID映射 mGidToNid.put(node.getGid(), sqlNote.getId()); 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) { return; } - SqlNote sqlNote; - // update the note locally - sqlNote = new SqlNote(mContext, c); - sqlNote.setContent(node.getLocalJSONFromContent()); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置远程内容 - Long parentId = (node instanceof Task) ? mGidToNid.get(((Task) node).getParent().getGid()) - : new Long(Notes.ID_ROOT_FOLDER); + // 设置父文件夹(根据远程任务的父列表GID查找本地对应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"); + Log.e(TAG, "无法找到任务的父文件夹ID"); + throw new ActionFailureException("无法更新本地节点"); } sqlNote.setParentId(parentId.longValue()); - sqlNote.commit(true); + sqlNote.commit(true); // 提交更新(更新同步状态) - // update meta info + // 更新元数据 updateRemoteMeta(node.getGid(), sqlNote); } + /** + * 将本地节点添加到远程Google Tasks + * @param node 本地节点(可能为null,表示需要从游标创建) + * @param c 本地数据库游标 + * @throws NetworkFailureException 网络异常 + */ private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote = new SqlNote(mContext, c); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 Node n; - // update remotely + // 更新到远程 if (sqlNote.isNoteType()) { + // 处理笔记 Task task = new Task(); - task.setContentByLocalJSON(sqlNote.getContent()); + task.setContentByLocalJSON(sqlNote.getContent()); // 从本地内容初始化任务 + // 获取父文件夹的GID 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"); + Log.e(TAG, "无法找到任务的父文件夹"); + throw new ActionFailureException("无法添加远程任务"); } - mGTaskListHashMap.get(parentGid).addChildTask(task); + mGTaskListHashMap.get(parentGid).addChildTask(task); // 添加到父列表 - GTaskClient.getInstance().createTask(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; @@ -656,6 +695,7 @@ public class GTaskManager { else folderName += sqlNote.getSnippet(); + // 检查文件夹是否已存在 Iterator> iter = mGTaskListHashMap.entrySet().iterator(); while (iter.hasNext()) { Map.Entry entry = iter.next(); @@ -671,87 +711,106 @@ public class GTaskManager { } } - // 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); + 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); - sqlNote.resetLocalModified(); - sqlNote.commit(true); + sqlNote.commit(false); // 提交(不更新同步状态) + sqlNote.resetLocalModified(); // 重置本地修改标记 + sqlNote.commit(true); // 再次提交(更新同步状态) - // gid-id mapping + // 更新GID与本地ID映射 mGidToNid.put(n.getGid(), sqlNote.getId()); mNidToGid.put(sqlNote.getId(), n.getGid()); } + /** + * 更新远程节点(根据本地变更) + * @param node 远程节点 + * @param c 本地数据库游标 + * @throws NetworkFailureException 网络异常 + */ private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { if (mCancelled) { return; } - SqlNote sqlNote = new SqlNote(mContext, c); + SqlNote sqlNote = new SqlNote(mContext, c); // 从游标创建SqlNote对象 - // update remotely - node.setContentByLocalJSON(sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(node); + // 更新远程节点 + 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(); + TaskList preParentList = task.getParent(); // 原父列表 - String curParentGid = mNidToGid.get(sqlNote.getParentId()); + String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 当前父列表GID if (curParentGid == null) { - Log.e(TAG, "cannot find task's parent tasklist"); - throw new ActionFailureException("cannot update remote task"); + Log.e(TAG, "无法找到任务的父文件夹"); + throw new ActionFailureException("无法更新远程任务"); } - TaskList curParentList = mGTaskListHashMap.get(curParentGid); + TaskList curParentList = mGTaskListHashMap.get(curParentGid); // 当前父列表 + // 如果父列表变更,执行移动操作 if (preParentList != curParentList) { - preParentList.removeChildTask(task); - curParentList.addChildTask(task); - GTaskClient.getInstance().moveTask(task, preParentList, curParentList); + preParentList.removeChildTask(task); // 从原列表移除 + curParentList.addChildTask(task); // 添加到新列表 + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 执行移动 } } - // clear local modified flag + // 清除本地修改标记 sqlNote.resetLocalModified(); sqlNote.commit(true); } + /** + * 更新远程元数据(关联任务与本地笔记) + * @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); + MetaData metaData = mMetaHashMap.get(gid); // 获取元数据 if (metaData != null) { + // 元数据已存在,更新内容 metaData.setMeta(gid, sqlNote.getContent()); - GTaskClient.getInstance().addUpdateNode(metaData); + GTaskClient.getInstance().addUpdateNode(metaData); // 添加到批量更新队列 } else { + // 元数据不存在,创建新的 metaData = new MetaData(); metaData.setMeta(gid, sqlNote.getContent()); - mMetaList.addChildTask(metaData); - mMetaHashMap.put(gid, metaData); - GTaskClient.getInstance().createTask(metaData); + mMetaList.addChildTask(metaData); // 添加到元数据列表 + mMetaHashMap.put(gid, metaData); // 缓存元数据 + GTaskClient.getInstance().createTask(metaData); // 创建远程任务(元数据以任务形式存储) } } } + /** + * 刷新本地同步ID(更新本地笔记的最后同步时间) + * @throws NetworkFailureException 网络异常 + */ private void refreshLocalSyncId() throws NetworkFailureException { if (mCancelled) { return; } - // get the latest gtask list + // 重新获取最新的远程任务列表 mGTaskHashMap.clear(); mGTaskListHashMap.clear(); mMetaHashMap.clear(); @@ -759,28 +818,29 @@ public class GTaskManager { 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"); + "(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); + // 更新本地笔记的同步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"); + Log.e(TAG, "找不到对应的远程节点"); + throw new ActionFailureException("同步后部分本地项没有GID"); } } } else { - Log.w(TAG, "failed to query local note to refresh sync id"); + Log.w(TAG, "查询本地笔记以刷新同步ID失败"); } } finally { if (c != null) { @@ -790,11 +850,18 @@ public class GTaskManager { } } + /** + * 获取当前同步的Google账号 + * @return 账号名称 + */ public String getSyncAccount() { return GTaskClient.getInstance().getSyncAccount().name; } + /** + * 取消同步操作 + */ public void cancelSync() { mCancelled = true; } -} +} \ No newline at end of file