From 275ae6f5973c97f28a74af13acd571f4452b39f2 Mon Sep 17 00:00:00 2001 From: pq5n3hobs Date: Fri, 10 Jan 2025 16:07:59 +0800 Subject: [PATCH] gtask-remote-3 --- GTaskManager.java | 443 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 GTaskManager.java diff --git a/GTaskManager.java b/GTaskManager.java new file mode 100644 index 0000000..ea7b4b7 --- /dev/null +++ b/GTaskManager.java @@ -0,0 +1,443 @@ +/* + * Copyright (c) 2010 - 2011, The MiCode Open Source Community (www.micode.net) + * + * 遵循 Apache 许可证 2.0 版(“许可证”); + * 除非遵守许可证,否则不得使用此文件。 + * 你可以在以下网址获取许可证副本: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 除非适用法律要求或书面同意, + * 根据许可证分发的软件按“原样”分发, + * 不附带任何明示或暗示的保证或条件。 + * 请参阅许可证,了解具体的权限和限制。 + */ + +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 任务(GTask)的同步操作,包括登录、获取任务列表、同步本地和远程数据等 +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; + + // 用于获取认证令牌的 Activity + private Activity mActivity; + // 上下文对象 + private Context mContext; + // 内容解析器,用于操作本地数据 + private ContentResolver mContentResolver; + // 同步是否正在进行的标志 + private boolean mSyncing; + // 同步是否被取消的标志 + private boolean mCancelled; + // 存储远程任务列表的 HashMap,键为任务列表的 GID,值为 TaskList 对象 + private HashMap mGTaskListHashMap; + // 存储远程节点(任务或任务列表)的 HashMap,键为节点的 GID,值为 Node 对象 + private HashMap mGTaskHashMap; + // 存储元数据的 HashMap,键为相关的 GID,值为 MetaData 对象 + private HashMap mMetaHashMap; + // 元数据任务列表 + private TaskList mMetaList; + // 存储本地已删除笔记 ID 的 HashSet + private HashSet mLocalDeleteIdMap; + // 从 GID 到本地笔记 ID 的映射 HashMap + private HashMap mGidToNid; + // 从本地笔记 ID 到 GID 的映射 HashMap + private HashMap mNidToGid; + + // 私有构造函数,用于初始化 GTaskManager 的成员变量 + private GTaskManager() { + mSyncing = false; + mCancelled = false; + mGTaskListHashMap = new HashMap(); + mGTaskHashMap = new HashMap(); + mMetaHashMap = new HashMap(); + mMetaList = null; + mLocalDeleteIdMap = new HashSet(); + mGidToNid = new HashMap(); + mNidToGid = new HashMap(); + } + + // 获取 GTaskManager 单例实例的方法 + public static synchronized GTaskManager getInstance() { + if (mInstance == null) { + mInstance = new GTaskManager(); + } + return mInstance; + } + + // 设置用于获取认证令牌的 Activity 的方法 + public synchronized void setActivityContext(Activity activity) { + // 用于获取认证令牌 + 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(); + + // 登录 Google 任务 + if (!mCancelled) { + if (!client.login(mActivity)) { + throw new NetworkFailureException("login google task failed"); + } + } + + // 从 Google 获取任务列表 + asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list)); + initGTaskList(); + + // 执行内容同步工作 + 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; + } + + // 初始化远程任务列表的方法 + private void initGTaskList() throws NetworkFailureException { + // 如果同步被取消,直接返回 + if (mCancelled) + return; + // 获取 GTaskClient 实例 + GTaskClient client = GTaskClient.getInstance(); + try { + // 获取远程任务列表的 JSON 数组 + JSONArray jsTaskLists = client.getTaskLists(); + + // 首先初始化元数据任务列表 + mMetaList = null; + for (int i = 0; i < jsTaskLists.length(); i++) { + // 获取每个任务列表的 JSONObject + JSONObject object = jsTaskLists.getJSONObject(i); + // 获取任务列表的 GID + 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); + + // 加载元数据 + JSONArray jsMetas = client.getTaskList(gid); + for (int j = 0; j < jsMetas.length(); j++) { + // 获取每个元数据的 JSONObject + object = (JSONObject) jsMetas.getJSONObject(j); + // 创建元数据对象 + MetaData metaData = new MetaData(); + // 根据远程 JSON 数据设置元数据的内容 + metaData.setContentByRemoteJSON(object); + // 如果元数据值得保存 + if (metaData.isWorthSaving()) { + // 将元数据添加到元数据任务列表 + mMetaList.addChildTask(metaData); + // 如果元数据有 GID,将其添加到元数据 HashMap 中 + if (metaData.getGid()!= null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); + } + } + } + } + } + + // 如果元数据任务列表不存在,创建一个 + if (mMetaList == null) { + mMetaList = new TaskList(); + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); + GTaskClient.getInstance().createTaskList(mMetaList); + } + + // 初始化任务列表 + for (int i = 0; i < jsTaskLists.length(); i++) { + // 获取每个任务列表的 JSONObject + object = jsTaskLists.getJSONObject(i); + // 获取任务列表的 GID + 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(); + // 根据远程 JSON 数据设置任务列表的内容 + tasklist.setContentByRemoteJSON(object); + // 将任务列表添加到任务列表 HashMap 中 + mGTaskListHashMap.put(gid, tasklist); + // 将任务列表添加到节点 HashMap 中 + mGTaskHashMap.put(gid, tasklist); + + // 加载任务 + JSONArray jsTasks = client.getTaskList(gid); + for (int j = 0; j < jsTasks.length(); j++) { + // 获取每个任务的 JSONObject + object = (JSONObject) jsTasks.getJSONObject(j); + // 获取任务的 GID + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); + // 创建任务对象 + Task task = new Task(); + // 根据远程 JSON 数据设置任务的内容 + task.setContentByRemoteJSON(object); + // 如果任务值得保存 + if (task.isWorthSaving()) { + // 设置任务的元信息 + task.setMetaInfo(mMetaHashMap.get(gid)); + // 将任务添加到任务列表 + tasklist.addChildTask(task); + // 将任务添加到节点 HashMap 中 + mGTaskHashMap.put(gid, task); + } + } + } + } + } catch (JSONException e) { + 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; + Node node; + + // 清空本地已删除笔记 ID 的集合 + mLocalDeleteIdMap.clear(); + + // 如果同步被取消,直接返回 + if (mCancelled) { + return; + } + + // 处理本地已删除的笔记 + 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 + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 获取对应的节点 + node = mGTaskHashMap.get(gid); + if (node!= null) { + // 从节点 HashMap 中移除该节点 + 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; + } + } + + // 首先同步文件夹 + syncFolder(); + + // 处理本地数据库中已存在的笔记 + 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 + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 获取对应的节点 + node = mGTaskHashMap.get(gid); + if (node!= null) { + // 从节点 HashMap 中移除该节点 + 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 { + // 如果 GID 为空,说明是本地新增的笔记 + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + syncType = Node.SYNC_ACTION_ADD_REMOTE; + } else { + // 否则是远程已删除的笔记 + 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; + } + } + + // 处理剩余的未处理节点 + Iterator> iter = mGTaskHashMap.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + node = entry.getValue(); + // 执行添加到本地的同步操作 + doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null); + } + + // 如果同步未被取消 + if (!mCancelled) { + // 批量删除本地已删除的笔记 + if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) { + throw new ActionFailureException("failed to batch-delete local deleted notes"); + } + } + + // 如果同步未被取消 + if (!mCancelled) { + // 提交更新 + GTaskClient.getInstance().commitUpdate(); + // 刷新本地同步 ID + refreshLocalSyncId(); + } + } + + // 同步文件夹的方法 + private void syncFolder() throws NetworkFailureException { + Cursor c = null; + String gid; + Node node; + int syncType; + + // 如果同步被取消,直接返回 + if (mCancelled) { + return; + } + + // 处理根文件夹 + 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 + gid = c.getString(SqlNote.GTASK_ID_COLUMN); + // 获取对应的节点 + node = mGTaskHashMap.get(gid); + if (node!= null) { + // 从节点 HashMap 中移除该节点 + m \ No newline at end of file