/* * 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