You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
xiaomi/GTaskManager.java

443 lines
18 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* 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<String, TaskList> mGTaskListHashMap;
// 存储远程节点(任务或任务列表)的 HashMap键为节点的 GID值为 Node 对象
private HashMap<String, Node> mGTaskHashMap;
// 存储元数据的 HashMap键为相关的 GID值为 MetaData 对象
private HashMap<String, MetaData> mMetaHashMap;
// 元数据任务列表
private TaskList mMetaList;
// 存储本地已删除笔记 ID 的 HashSet
private HashSet<Long> mLocalDeleteIdMap;
// 从 GID 到本地笔记 ID 的映射 HashMap
private HashMap<String, Long> mGidToNid;
// 从本地笔记 ID 到 GID 的映射 HashMap
private HashMap<Long, String> mNidToGid;
// 私有构造函数,用于初始化 GTaskManager 的成员变量
private GTaskManager() {
mSyncing = false;
mCancelled = false;
mGTaskListHashMap = new HashMap<String, TaskList>();
mGTaskHashMap = new HashMap<String, Node>();
mMetaHashMap = new HashMap<String, MetaData>();
mMetaList = null;
mLocalDeleteIdMap = new HashSet<Long>();
mGidToNid = new HashMap<String, Long>();
mNidToGid = new HashMap<Long, String>();
}
// 获取 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<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Node> 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