diff --git a/src/一千注释.txt b/src/一千注释.txt new file mode 100644 index 0000000..114ef4b --- /dev/null +++ b/src/一千注释.txt @@ -0,0 +1,1027 @@ +/* + * 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; // Google任务管理功能的包 + +import android.app.Activity; // 导入Activity类 +import android.content.ContentResolver; // 用于访问内容模型的类 +import android.content.ContentUris; // 处理内容URI的实用类 +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; // Google 任务的元数据 +import net.micode.notes.gtask.data.Node; // 任务的节点结构 +import net.micode.notes.gtask.data.SqlNote; // SQL便签的数据结构 +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; // Google 任务字符串的工具类 + +import org.json.JSONArray; // 处理JSON数组的类 +import org.json.JSONException; // JSON操作的异常 +import org.json.JSONObject; // 处理JSON对象的类 + +import java.util.HashMap; // HashMap用于数据存储 +import java.util.HashSet; // HashSet用于存储唯一集合 +import java.util.Iterator; // 用于迭代集合的类 +import java.util.Map; // Map接口 + +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; // 标记同步是否已取消 + + // 用于管理Google任务和元数据的Map + private HashMap mGTaskListHashMap; // 存储任务列表的映射 + private HashMap mGTaskHashMap; // 存储任务的映射 + private HashMap mMetaHashMap; // 存储元数据的映射 + + private TaskList mMetaList; // 元任务列表 + private HashSet mLocalDeleteIdMap; // 用于跟踪本地删除的ID集合 + private HashMap mGidToNid; // Google ID到便签ID的映射 + private HashMap mNidToGid; // 便签ID到Google ID的映射 + + // 私有构造函数以实现单例模式 + private GTaskManager() { + mSyncing = false; // 初始化为未同步 + mCancelled = false; // 初始化为未取消 + mGTaskListHashMap = new HashMap<>(); // 初始化任务列表映射 + mGTaskHashMap = new HashMap<>(); // 初始化任务映射 + mMetaHashMap = new HashMap<>(); // 初始化元数据映射 + mMetaList = null; // 初始化元列表为null + mLocalDeleteIdMap = new HashSet<>(); // 创建本地删除ID集合 + mGidToNid = new HashMap<>(); // 初始化Google到便签ID的映射 + mNidToGid = new HashMap<>(); // 初始化便签到Google ID的映射 + } + + // 获取单例访问方法 + 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; // 标记为未取消 + + // 清空任务和元数据的HashMap + mGTaskListHashMap.clear(); + mGTaskHashMap.clear(); + mMetaHashMap.clear(); + mLocalDeleteIdMap.clear(); + mGidToNid.clear(); + mNidToGid.clear(); + + try { + GTaskClient client = GTaskClient.getInstance(); // 获取Google任务客户端 + 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 { + // 清空HashMap数据 + 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(); // 获取Google任务客户端 + try { + JSONArray jsTaskLists = client.getTaskLists(); // 获取任务列表的JSON数组 + + // 首先初始化元数据列表 + mMetaList = null; // 初始化元列表为null + for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历所有任务列表 + JSONObject object = jsTaskLists.getJSONObject(i); // 获取任务列表对象 + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务列表ID + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取任务列表名称 + + // 检查是否为元文件夹 + if (name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + mMetaList = new TaskList(); // 实例化元任务列表 + mMetaList.setContentByRemoteJSON(object); // 设置任务列表内容 + + // 加载元数据 + JSONArray jsMetas = client.getTaskList(gid); // 获取元数据的JSON数组 + for (int j = 0; j < jsMetas.length(); j++) { // 遍历每个元数据对象 + object = (JSONObject) jsMetas.getJSONObject(j); // 获取元数据对象 + MetaData metaData = new MetaData(); // 实例化元数据 + metaData.setContentByRemoteJSON(object); // 设置元数据内容 + if (metaData.isWorthSaving()) { // 检查元数据是否值得保存 + mMetaList.addChildTask(metaData); // 添加到元任务列表 + if (metaData.getGid() != null) { + mMetaHashMap.put(metaData.getRelatedGid(), metaData); // 将元数据ID映射到元数据对象 + } + } + } + } + } + + // 如果元数据列表不存在,则创建它 + if (mMetaList == null) { + mMetaList = new TaskList(); // 创建新的任务列表 + mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META); // 设置名称 + GTaskClient.getInstance().createTaskList(mMetaList); // 在Google任务中创建任务列表 + } +// 初始化任务列表 +for (int i = 0; i < jsTaskLists.length(); i++) { // 遍历所有任务列表 + JSONObject object = jsTaskLists.getJSONObject(i); // 获取当前任务列表的JSONObject + String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务列表的ID + String name = object.getString(GTaskStringUtils.GTASK_JSON_NAME); // 获取任务列表的名称 + + // 如果任务列表名称以指定前缀开头,并且不是元文件夹 + if (name.startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX) + && !name.equals(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_META)) { + TaskList tasklist = new TaskList(); // 创建一个新的任务列表实例 + tasklist.setContentByRemoteJSON(object); // 设置任务列表的内容 + mGTaskListHashMap.put(gid, tasklist); // 将任务列表存入任务列表映射 + mGTaskHashMap.put(gid, tasklist); // 将任务列表存入任务映射 + + // 加载任务 + JSONArray jsTasks = client.getTaskList(gid); // 获取当前任务列表下的任务 + for (int j = 0; j < jsTasks.length(); j++) { // 遍历任务 + object = (JSONObject) jsTasks.getJSONObject(j); // 获取当前任务的JSONObject + gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); // 获取任务ID + Task task = new Task(); // 创建新的任务实例 + task.setContentByRemoteJSON(object); // 设置任务的内容 + if (task.isWorthSaving()) { // 检查任务是否值得保存 + task.setMetaInfo(mMetaHashMap.get(gid)); // 设置任务的元信息 + tasklist.addChildTask(task); // 将任务添加到任务列表中 + mGTaskHashMap.put(gid, task); // 将任务存入任务映射 + } + } + } +} +} catch (JSONException e) { // 处理JSON异常 + Log.e(TAG, e.toString()); // 记录错误信息 + e.printStackTrace(); // 打印堆栈跟踪 + throw new ActionFailureException("initGTaskList: handling JSONObject failed"); // 抛出动作失败异常 +} + +private void syncContent() throws NetworkFailureException { + int syncType; // 同步类型 + Cursor c = null; // 游标用于查询结果 + String gid; // 任务的Google ID + Node node; // 节点对象 + + mLocalDeleteIdMap.clear(); // 清空本地删除ID集合 + + 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 = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取任务ID + node = mGTaskHashMap.get(gid); // 从任务映射中获取节点 + if (node != null) { // 如果节点存在 + mGTaskHashMap.remove(gid); // 从映射中移除该节点 + doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c); // 执行远程删除操作 + } + + mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN)); // 将本地删除的便签ID添加到集合 + } + } else { + Log.w(TAG, "failed to query trash folder"); // 记录查询回收站失败的警告 + } + } finally { + if (c != null) { // 确保游标关闭 + c.close(); + c = null; // 将游标设为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 = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取任务ID + node = mGTaskHashMap.get(gid); // 从任务映射中获取节点 + if (node != null) { // 如果节点存在 + mGTaskHashMap.remove(gid); // 从映射中移除该节点 + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 将Google ID映射到便签ID + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 将便签ID映射到Google ID + syncType = node.getSyncAction(c); // 获取同步操作类型 + } else { + // 检查便签ID是否为空 + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 如果便签ID为空,标记为本地新增 + 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"); // 记录查询数据库中现有便签失败的警告 + } + + } +// 针对本地已有的文件夹进行同步 +try { + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type=? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_FOLDER), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); // 查询所有类型为文件夹且不在回收站中的便签 + if (c != null) { // 如果查询结果不为空 + while (c.moveToNext()) { // 遍历查询到的文件夹 + gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取文件夹的Google ID + node = mGTaskHashMap.get(gid); // 从任务映射中获取相应节点 + if (node != null) { // 如果找到对应的节点 + mGTaskHashMap.remove(gid); // 从任务映射中移除该节点 + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); // 将Google ID映射到本地ID + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); // 将本地ID映射到Google ID + syncType = node.getSyncAction(c); // 获取当前节点的同步操作类型 + } else { + // 如果节点不为null,检查Google ID是否为空 + if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) { + // 如果Google ID为空,标记为本地新增 + 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 folder"); // 记录查询现有文件夹失败的警告 + } +} finally { + if (c != null) { + c.close(); // 确保游标关闭 + c = null; // 将游标设为null + } +} + +// 针对远程新增的文件夹进行同步 +Iterator> iter = mGTaskListHashMap.entrySet().iterator(); // 遍历任务列表的映射 +while (iter.hasNext()) { // 循环迭代 + Map.Entry entry = iter.next(); // 获取当前条目 + gid = entry.getKey(); // 获取Google ID + node = entry.getValue(); // 获取任务列表对象 + if (mGTaskHashMap.containsKey(gid)) { // 如果任务映射中存在该Google ID + 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: // 更新冲突 + // 合并两个修改可能是个好主意,这里仅简单使用本地更新 + updateRemoteNode(node, c); // 仅进行远程更新 + break; + case Node.SYNC_ACTION_NONE: // 无操作 + break; + case Node.SYNC_ACTION_ERROR: // 错误 + default: // 默认情况 + throw new ActionFailureException("unknown 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); // 创建根文件夹的SqlNote + } else if (node.getName().equals( + GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) { + sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER); // 创建通话记录文件夹的SqlNote + } else { + sqlNote = new SqlNote(mContext); // 创建一个新的SqlNote实例 + sqlNote.setContent(node.getLocalJSONFromContent()); // 设置便签内容 + sqlNote.setParentId(Notes.ID_ROOT_FOLDER); // 将父ID设置为根文件夹 + } + } else { + sqlNote = new SqlNote(mContext); // 创建新的SqlNote实例 + JSONObject js = node.getLocalJSONFromContent(); // 获取节点的本地JSON内容 + try { + if (js.has(GTaskStringUtils.META_HEAD_NOTE)) { // 检查是否有元数据 + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 获取元数据对象 + if (note.has(NoteColumns.ID)) { // 如果元数据包含ID + long id = note.getLong(NoteColumns.ID); // 获取ID + if (DataUtils.existInNoteDatabase(mContentResolver, id)) { + // 如果ID在数据库中存在,需要创建一个新的ID + note.remove(NoteColumns.ID); // 从元数据中移除ID + } + } + } + + if (js.has(GTaskStringUtils.META_HEAD_DATA)) { // 检查是否有数据头 +// 初始化SqlNote以便更新便签到本地 +sqlNote = new SqlNote(mContext, c); +sqlNote.setContent(node.getLocalJSONFromContent()); // 设置便签内容为本地JSON数据 + +// 获取父任务ID。如果节点是Task类型,使用映射获取父节点,否则默认使用根文件夹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"); // 记录错误,无法找到父节点ID + throw new ActionFailureException("cannot update local node"); // 抛出异常,无法更新本地节点 +} +sqlNote.setParentId(parentId.longValue()); // 设置父节点ID +sqlNote.commit(true); // 提交更新 + +// 更新元数据 +updateRemoteMeta(node.getGid(), sqlNote); // 将远程元数据更新至当前节点 +} + +// 添加远程节点 +private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; // 如果已取消,直接返回 + } + + SqlNote sqlNote = new SqlNote(mContext, c); // 创建SqlNote对象 + Node n; + + // 远程更新节点 + if (sqlNote.isNoteType()) { // 如果是便签类型 + Task task = new Task(); // 创建任务对象 + task.setContentByLocalJSON(sqlNote.getContent()); // 设置任务内容 + + String parentGid = mNidToGid.get(sqlNote.getParentId()); // 获取父任务的Google ID + 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; // 将新创建的任务赋值给节点 + + // 更新元数据 + updateRemoteMeta(task.getGid(), sqlNote); // 将元数据更新至当前任务 + } else { + TaskList tasklist = null; // 初始化任务列表指针 + + // 跳过已存在的文件夹 + 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> iter = mGTaskListHashMap.entrySet().iterator(); // 遍历任务列表 + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + String gid = entry.getKey(); // 获取Google ID + TaskList list = entry.getValue(); // 获取任务列表 + + if (list.getName().equals(folderName)) { // 如果文件夹名匹配 + tasklist = list; // 替换为匹配的任务列表 + if (mGTaskHashMap.containsKey(gid)) { // 如果映射中存在该Google ID + mGTaskHashMap.remove(gid); // 从映射中移除该ID + } + break; // 跳出循环 + } + } + + // 如果没有匹配的,可以添加新的 + if (tasklist == null) { // 如果没有找到文件夹,则创建新的文件夹 + tasklist = new TaskList(); // 创建新的任务列表 + tasklist.setContentByLocalJSON(sqlNote.getContent()); // 设置任务列表内容 + GTaskClient.getInstance().createTaskList(tasklist); // 创建远程任务列表 + mGTaskListHashMap.put(tasklist.getGid(), tasklist); // 将任务列表添加到映射 + } + n = (Node) tasklist; // 将新创建的任务列表赋值给节点 + } + + // 更新本地便签 + sqlNote.setGtaskId(n.getGid()); // 设置当前节点的Google ID + sqlNote.commit(false); // 提交更新,不会覆盖本地修改 + sqlNote.resetLocalModified(); // 重置本地修改标记 + sqlNote.commit(true); // 再次提交更新 + + // 进行gid-id映射更新 + mGidToNid.put(n.getGid(), sqlNote.getId()); // 将Google ID映射到本地ID + mNidToGid.put(sqlNote.getId(), n.getGid()); // 将本地ID映射到Google ID +} + +// 更新远程节点 +private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException { + if (mCancelled) { + return; // 如果已取消,直接返回 + } + + SqlNote sqlNote = new SqlNote(mContext, c); // 根据游标创建SqlNote对象 + + // 远程更新逻辑 + node.setContentByLocalJSON(sqlNote.getContent()); // 更新节点内容 + GTaskClient.getInstance().addUpdateNode(node); // 向GTask客户端添加更新 + + // 更新元数据 + updateRemoteMeta(node.getGid(), sqlNote); // 更新远程元数据 + + // 如果需要,移动任务 + if (sqlNote.isNoteType()) { // 如果是便签类型 + Task task = (Task) node; // 转换为任务类型 + TaskList preParentList = task.getParent(); // 获取任务的原父任务列表 + + String curParentGid = mNidToGid.get(sqlNote.getParentId()); // 获取当前父任务的Google ID + 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); // // 移动任务 + GTaskClient.getInstance().moveTask(task, preParentList, curParentList); // 调用GTaskClient移动任务到新父列表 + } + } + + // 清除本地修改标志 + sqlNote.resetLocalModified(); // 重置本地修改标记 + sqlNote.commit(true); // 提交修改 +} + +// 更新远程元数据 +private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException { + if (sqlNote != null && sqlNote.isNoteType()) { // 确保sqlNote不为空且为便签类型 + MetaData metaData = mMetaHashMap.get(gid); // 根据gid获取元数据 + if (metaData != null) { // 如果元数据存在 + metaData.setMeta(gid, sqlNote.getContent()); // 更新元数据内容 + GTaskClient.getInstance().addUpdateNode(metaData); // 向GTask客户端添加更新 + } 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; // 如果已取消,直接返回 + } + + // 获取最新的Google任务列表 + mGTaskHashMap.clear(); // 清空现有的Google任务映射 + mGTaskListHashMap.clear(); // 清空现有的任务列表映射 + mMetaHashMap.clear(); // 清空现有的元数据映射 + initGTaskList(); // 初始化Google任务列表 + + Cursor c = null; // 初始化游标 + try { + // 查询本地便签,排除系统类型和回收站便签 + c = mContentResolver.query(Notes.CONTENT_NOTE_URI, SqlNote.PROJECTION_NOTE, + "(type<>? AND parent_id<>?)", new String[] { + String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER) + }, NoteColumns.TYPE + " DESC"); + if (c != null) { + while (c.moveToNext()) { // 遍历查询结果 + String gid = c.getString(SqlNote.GTASK_ID_COLUMN); // 获取Google ID + Node node = mGTaskHashMap.get(gid); // 从映射中查找节点 + if (node != null) { // 如果节点存在 + mGTaskHashMap.remove(gid); // 移除映射中的该节点 + ContentValues values = new ContentValues(); // 创建ContentValues实例 + values.put(NoteColumns.SYNC_ID, node.getLastModified()); // 设置同步ID为节点的最后修改时间 + // 更新数据库 + mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, + c.getLong(SqlNote.ID_COLUMN)), values, null, null); + } else { // 如果节点不存在 + Log.e(TAG, "something is missed"); // 记录错误,某些本地条目缺少gid + throw new ActionFailureException( + "some local items don't have gid after sync"); // 抛出异常,某些本地条目SYNC后没有GID + } + } + } else { + Log.w(TAG, "failed to query local note to refresh sync id"); // 记录警告,查询本地便签失败 + } + } finally { + if (c != null) { + c.close(); // 释放游标资源 + c = null; // 将游标设置为null + } + } +} + +// 获取同步账号的名称 +public String getSyncAccount() { + return GTaskClient.getInstance().getSyncAccount().name; // 返回同步账户的名称 +} + +// 取消同步 +public void cancelSync() { + mCancelled = true; // 将取消标志设为true,表示请求停止同步 +} + + + + + + + + + + + + + + + + + + android:dataExtractionRules="@xml/data_extraction_rules" + android:fullBackupContent="@xml/backup_rules" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/Theme.Notesmaster" + tools:targetApi="31"> + + + + android:configChanges="keyboardHidden|orientation|screenSize" + android:label="@string/app_name" + android:launchMode="singleTop" + android:theme="@style/NoteTheme" + android:uiOptions="splitActionBarWhenNarrow" + android:windowSoftInputMode="adjustPan" + android:exported="true"> + + + + + + + + + + android:configChanges="keyboardHidden|orientation|screenSize" + android:launchMode="singleTop" + android:theme="@style/NoteTheme" + android:exported="true"> + + + + + + + + + + + + + + + + + + + + + + + + + + android:authorities="micode_notes" + android:multiprocess="true" /> + + + + android:label="@string/app_widget2x2" + android:exported="true"> + + + + + + + + + + + android:label="@string/app_widget4x4" + android:exported="true"> + + + + + + + + + + + + + + + + + + + + + + android:label="@string/app_name" + android:launchMode="singleInstance" + android:theme="@android:style/Theme.Holo.Wallpaper.NoTitleBar" > + + + + + android:label="@string/preferences_title" + android:launchMode="singleTop" + android:theme="@android:style/Theme.Holo.Light" /> + + + + + android:exported="false" /> + + + + + + + +/* + * 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.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; + +public class GTaskSyncService extends Service { + // 常量定义 + public final static String ACTION_STRING_NAME = "sync_action_type"; + + public final static int ACTION_START_SYNC = 0; // 启动同步操作的常量 + + public final static int ACTION_CANCEL_SYNC = 1; // 取消同步操作的常量 + + public final static int ACTION_INVALID = 2; // 无效操作的常量 + + public final static String GTASK_SERVICE_BROADCAST_NAME = "net.micode.notes.gtask.remote.gtask_sync_service"; + + public final static String GTASK_SERVICE_BROADCAST_IS_SYNCING = "isSyncing"; // 表示是否正在同步的广播 + + public final static String GTASK_SERVICE_BROADCAST_PROGRESS_MSG = "progressMsg"; // 同步进度消息的广播 + + private static GTaskASyncTask mSyncTask = null; // 存储当前的异步任务实例 + + private static String mSyncProgress = ""; // 存储同步进度消息 + + // 开始同步的方法 + private void startSync() { + if (mSyncTask == null) { + mSyncTask = new GTaskASyncTask(this, new GTaskASyncTask.OnCompleteListener() { + // 同步完成回调 + public void onComplete() { + mSyncTask = null; // 重置同步任务 + sendBroadcast(""); // 发送广播,更新状态 + stopSelf(); // 停止服务 + } + }); + sendBroadcast(""); // 初始广播 + mSyncTask.execute(); // 执行异步同步任务 + } + } + + // 取消同步的方法 + private void cancelSync() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); // 调用任务的取消方法 + } + } + + @Override + public void onCreate() { + mSyncTask = null; // 服务创建时重置同步任务 + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Bundle bundle = intent.getExtras(); // 获取传递的附加信息 + if (bundle != null && bundle.containsKey(ACTION_STRING_NAME)) { + // 检查和处理传递过来的动作指令 + switch (bundle.getInt(ACTION_STRING_NAME, ACTION_INVALID)) { + case ACTION_START_SYNC: + startSync(); // 启动同步 + break; + case ACTION_CANCEL_SYNC: + cancelSync(); // 取消同步 + break; + default: + break; + } + return START_STICKY; // 保持服务运行 + } + return super.onStartCommand(intent, flags, startId); // 调用父类方法 + } + + @Override + public void onLowMemory() { + if (mSyncTask != null) { + mSyncTask.cancelSync(); // 内存不足时取消同步 + } + } + + @Override + public IBinder onBind(Intent intent) { + return null; // 该服务不支持绑定 + } + + // 发送广播的方法 + public void sendBroadcast(String msg) { + mSyncProgress = msg; // 更新同步进度消息 + Intent intent = new Intent(GTASK_SERVICE_BROADCAST_NAME); // 创建广播意图 + intent.putExtra(GTASK_SERVICE_BROADCAST_IS_SYNCING, mSyncTask != null); // 是否正在同步 + intent.putExtra(GTASK_SERVICE_BROADCAST_PROGRESS_MSG, msg); // 添加进度消息 + sendBroadcast(intent); // 发送广播 + } + + // 启动同步的静态方法 + public static void startSync(Activity activity) { + GTaskManager.getInstance().setActivityContext(activity); // 设置活动上下文 + Intent intent = new Intent(activity, GTaskSyncService.class); // 创建意图以启动服务 + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_START_SYNC); + activity.startService(intent); // 启动服务 + } + + // 取消同步的静态方法 + public static void cancelSync(Context context) { + Intent intent = new Intent(context, GTaskSyncService.class); // 创建意图以取消服务 + intent.putExtra(GTaskSyncService.ACTION_STRING_NAME, GTaskSyncService.ACTION_CANCEL_SYNC); + context.startService(intent); // 启动取消服务 + } + + // 检查是否正在同步 + public static boolean isSyncing() { + return mSyncTask != null; // 返回任务是否存在 + } + + // 获取同步进度消息 + public static String getProgressString() { + return mSyncProgress; // 返回当前的同步进度消息 + } +} +package net.micode.notes.gtask.data; + +import android.database.Cursor; +import android.util.Log; +import net.micode.notes.tool.GTaskStringUtils; +import org.json.JSONException; +import org.json.JSONObject; + +public class MetaData extends Task { + private final static String TAG = MetaData.class.getSimpleName(); + + private String mRelatedGid = null; + + // 设置元数据的方法 + public void setMeta(String gid, JSONObject metaInfo) { + try { + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); // 将 gid 插入到 metaInfo 中 + } catch (JSONException e) { + Log.e(TAG, "failed to put related gid"); // 异常处理,打印错误日志 + } + setNotes(metaInfo.toString()); // 将元数据转换成字符串并设置为笔记内容 + setName(GTaskStringUtils.META_NOTE_NAME); // 设置名称 + } + + // 获取相关 gid 的方法 + public String getRelatedGid() { + return mRelatedGid; // 返回相关 gid + } + + @Override + public boolean isWorthSaving() { + return getNotes() != null; // 如果有笔记内容则视为可保存 + } + + @Override + public void setContentByRemoteJSON(JSONObject js) { + super.setContentByRemoteJSON(js); // 调用父类方法 + if (getNotes() != null) { + try { + JSONObject metaInfo = new JSONObject(getNotes().trim()); // 解析笔记内容为 JSON 对象 + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); // 获取相关 gid + } catch (JSONException e) { + Log.w(TAG, "failed to get related gid"); // 异常处理 + mRelatedGid = null; // 失败时将相关 gid 设置为 null + } + } + } + + @Override + public void setContentByLocalJSON(JSONObject js) { + // 此方法不应被调用 + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + } + + @Override + public JSONObject getLocalJSONFromContent() { + // 此方法不应被调用 + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + } + + @Override + public int getSyncAction(Cursor c) { + // 此方法不应被调用 + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); + } +} \ No newline at end of file