diff --git a/t/gt.txt b/t/gt.txt new file mode 100644 index 0000000..81259c5 --- /dev/null +++ b/t/gt.txt @@ -0,0 +1,434 @@ +/* + * 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 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; + + // GTaskManager的单例实例 + private static GTaskManager mInstance = null; + + // 用于获取认证令牌的Activity + private Activity mActivity; + // 上下文对象 + private Context mContext; + // 内容解析器,用于与内容提供者交互 + private ContentResolver mContentResolver; + // 同步状态标志,true表示正在同步 + private boolean mSyncing; + // 取消同步标志,true表示同步已取消 + private boolean mCancelled; + // 存储Google任务列表的HashMap,键为任务列表的gid + private HashMap mGTaskListHashMap; + // 存储Google任务节点的HashMap,键为任务的gid + private HashMap mGTaskHashMap; + // 存储元数据的HashMap,键为相关gid + private HashMap mMetaHashMap; + // 元数据任务列表 + private TaskList mMetaList; + // 存储本地删除的笔记ID的HashSet + private HashSet mLocalDeleteIdMap; + // 用于映射gid到本地笔记ID的HashMap + private HashMap mGidToNid; + // 用于映射本地笔记ID到gid的HashMap + private HashMap mNidToGid; + + // 私有构造函数,初始化各种状态和数据结构 + 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(); + // 设置同步状态为正在进行,取消状态为false + 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 { + // 清空各种数据结构,设置同步状态为false + 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(); + try { + // 获取任务列表的JSON数组 + JSONArray jsTaskLists = client.getTaskLists(); + + // 首先初始化元数据列表 + 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)) { + mMetaList = new TaskList(); + mMetaList.setContentByRemoteJSON(object); + + // 加载元数据 + JSONArray jsMetas = client.getTaskList(gid); + 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); + } + } + } + } + } + + // 如果元数据列表不存在,则创建 + 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); + 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)) { + 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); + gid = object.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); + } + } + } + } + } 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 = c.getString(SqlNote.GTASK_ID_COLUMN); + 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)); + } + } 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 = c.getString(SqlNote.GTASK_ID_COLUMN); + node = mGTaskHashMap.get(gid); + if (node!= null) { + mGTaskHashMap.remove(gid); + mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN)); + mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); + syncType = node.getSyncAction(c); + } else { + 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"); + } + } + + // 检查是否取消同步,刷新本地同步ID + if (!mCancelled) { + GTaskClient.getInstance().commitUpdate(); + 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 = 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); + // 对于系统文件夹,仅在必要时更新远程名称 + 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; + } + } + + // 处理通话记录文件夹 + 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); + // 对于系统文件夹,仅在必要时更新远程名称 + 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(); \ No newline at end of file diff --git a/t/ne.txt b/t/ne.txt new file mode 100644 index 0000000..e69de29