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.
Notes/GTaskManager.java

999 lines
49 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)
*
* 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.
*/
/**
* @ProjectName: MiNote
* @Package: net.micode.notes.gtask.remote
* @ClassName: GTaskManager
* @Description: 管理 Google 任务的同步操作。该类包含了一些内部状态和方法,用于处理本地数据与远程 Google 任务的同步。
* 这个类具有如下功能:
* 初始化 GTaskManager 实例。
* 获取 GTaskManager 的单例实例。
* 设置 Activity 上下文。
* 执行本地同步和远程同步操作。
* 初始化 GTaskList将从 Google 获取的 JSON 数据转换为本地 TaskList。
* 处理本地内容同步操作,包括本地增加、删除和更新节点等操作。
* 更新远程 Meta 数据。
* 刷新本地同步 ID。
* 此外,代码中还包含了异常处理和日志记录,以及对线程取消标志的处理。
* @Author: cyh
*/
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;
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;
private HashMap<String, TaskList> mGTaskListHashMap;
private HashMap<String, Node> mGTaskHashMap;
private HashMap<String, MetaData> mMetaHashMap;
private TaskList mMetaList;
private HashSet<Long> mLocalDeleteIdMap;
private HashMap<String, Long> mGidToNid;
private HashMap<Long, String> mNidToGid;
/**
* @功能名GTaskManager()
* @功能描述: GTaskManager 类的构造函数。
* @实现过程:在对象初始化时,会进行一些初始设置和数据结构的初始化。
* @Author:cyh
*/
private GTaskManager() { //对象初始化函数
mSyncing = false; //正在同步,flase代表未执行
mCancelled = false; //全局标识flase代表可以执行
mGTaskListHashMap = new HashMap<String, TaskList>(); //<>代表Java的泛型,就是创建一个用类型作为参数的类。
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>(); //通过hashmap散列表建立映射
}
/**
* @功能名GTaskManager getInstance()
* @功能描述GTaskManager 类的静态方法 getInstance()。它用于获取 GTaskManager 的单例实例
* @实现过程:首先判断 mInstance 是否为 null如果为 null则说明还没有创建 GTaskManager 实例。如果 mInstance 为 null则通过调用私有构造函数 new GTaskManager() 创建一个新的 GTaskManager 实例。将新创建的实例赋值给 mInstance。最后返回 mInstance即返回 GTaskManager 的单例实例。
* @Author:cyh
*/
public static synchronized GTaskManager getInstance() { //可能运行在多线程环境下,使用语言级同步--synchronized
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
/**
* @功能名setActivityContext(Activity activity)
* @参数Activity activity
* @功能描述:接受一个 Activity 类型的参数,用于设置当前 Activity 的上下文
* @实现过程:将传入的 activity 赋值给成员变量 mActivity。
* @Author:cyh
*/
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
/**
* @功能名sync(Context context, GTaskASyncTask asyncTask)
* @参数Context context, GTaskASyncTask asyncTask
* @功能描述:执行任务同步操作。
* @实现过程:
* 首先检查 mSyncing 标志位,如果为 true则说明当前正在进行同步操作直接返回 STATE_SYNC_IN_PROGRESS 状态。
* 将传入的 context 参数赋值给成员变量 mContext并获取 ContentResolver 对象。
* 设置 mSyncing 标志位为 true表示同步操作开始。
* 清空相关数据结构(如哈希表、映射等),以便重新初始化。
* 通过调用 GTaskClient.getInstance() 获取 GTaskClient 的单例实例,并重置更新数组。
* 如果未取消同步操作,进行 Google 账号的登录认证,如果登录失败则抛出 NetworkFailureException 异常。
* 将 Google 上获取任务列表并转换为本地的 TaskList。
* 执行内容同步操作。
* 如果捕获到 NetworkFailureException 异常,则记录错误日志并返回 STATE_NETWORK_ERROR 状态。
* 如果捕获到 ActionFailureException 异常,则记录错误日志并返回 STATE_INTERNAL_ERROR 状态。
* 如果捕获到其他异常,则记录错误日志并返回 STATE_INTERNAL_ERROR 状态。
* 最后,在 finally 块中清空相关数据结构,并将 mSyncing 标志位设置为 false表示同步操作结束。
* 返回同步状态,如果取消了同步操作,则返回 STATE_SYNC_CANCELLED 状态,否则返回 STATE_SUCCESS 状态。
* @Author:cyh
*/
public int sync(Context context, GTaskASyncTask asyncTask) { //核心函数
if (mSyncing) {
Log.d(TAG, "Sync is in progress"); //创建日志文件调试信息debug
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 client = GTaskClient.getInstance(); //getInstance即为创建一个实例,client--客户机
client.resetUpdateArray();
// login google task
if (!mCancelled) {
if (!client.login(mActivity)) {
throw new NetworkFailureException("login google task failed");
}
}
// get the task list from google
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_init_list));
initGTaskList(); //获取Google上的JSONtasklist转为本地TaskList
// do content sync work
asyncTask.publishProgess(mContext.getString(R.string.sync_progress_syncing));
syncContent();
} catch (NetworkFailureException e) { //分为两种异常,此类异常为网络异常
Log.e(TAG, e.toString()); //创建日志文件调试信息error
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;
}
/**
* @功能名initGTaskList()
* @功能描述初始化GTask列表的方法。通过与GTask服务器进行通信获取任务列表和任务数据并将其存储在相应的数据结构中。
* @实现过程:
*首先代码从GTaskClient实例中获取任务列表的JSON对象数组。然后它遍历这个数组逐个解析每个JSON对象。对于元表名字以特定前缀开头代码创建一个TaskList对象并将其内容从远程JSON对象复制到该对象中。然后它获取该元表下的任务列表的JSON对象数组并遍历这个数组。对于每个任务代码创建一个MetaData对象并将其内容从远程JSON对象复制到该对象中。如果该任务值得保存由isWorthSaving()方法决定则将其添加到元表的子任务列表中并将其相关的gid作为键将MetaData对象添加到mMetaHashMap中。
* 如果元表不存在则代码创建一个新的元表并使用GTaskClient实例的createTaskList方法将其发送到服务器。
* 接下来代码再次遍历任务列表的JSON对象数组。对于以特定前缀开头但不是元表的任务列表代码创建一个TaskList对象并将其内容从远程JSON对象复制到该对象中。然后它获取该任务列表下的任务的JSON对象数组并遍历这个数组。对于每个任务代码创建一个Task对象并将其内容从远程JSON对象复制到该对象中。如果该任务值得保存代码将其关联的元数据设置为mMetaHashMap中对应的对象并将该任务添加到任务列表的子任务列表中并使用gid作为键将Task对象添加到mGTaskHashMap中。
* 最后如果出现JSON解析异常代码会打印错误信息并抛出ActionFailureException异常。
* @Author:cyh
*/
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
//Json对象是Name Value对(即子元素)的无序集合相当于一个Map对象。JsonObject类是bantouyan-json库对Json对象的抽象提供操纵Json对象的各种方法。
//其格式为{"key1":value1,"key2",value2....};key 必须是字符串。
//因为ajax请求不刷新页面但配合js可以实现局部刷新因此json常常被用来作为异步请求的返回对象使用。
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
mMetaList = null; //TaskList类型
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i); //JSONObject与JSONArray一个为对象一个为数组。此处取出单个JASONObject
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(); //MetaList意为元表,Tasklist类型此处为初始化
mMetaList.setContentByRemoteJSON(object); //将JSON中部分数据复制到自己定义的对象中相对应的数据name->mname...
// load meta data
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()) { //如果不值得保存metadata将不加入mMetaList
mMetaList.addChildTask(metaData);
if (metaData.getGid() != null) {
mMetaHashMap.put(metaData.getRelatedGid(), metaData);
}
}
}
}
}
// create meta list if not existed
if (mMetaList == null) {
mMetaList = new TaskList();
mMetaList.setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX
+ GTaskStringUtils.FOLDER_META);
GTaskClient.getInstance().createTaskList(mMetaList);
}
// init task list
for (int i = 0; i < jsTaskLists.length(); i++) {
JSONObject object = jsTaskLists.getJSONObject(i);
String gid = object.getString(GTaskStringUtils.GTASK_JSON_ID); //通过getString函数传入本地某个标志数据的名称获取其在远端的名称。
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(); //继承自Node
tasklist.setContentByRemoteJSON(object);
mGTaskListHashMap.put(gid, tasklist);
mGTaskHashMap.put(gid, tasklist);
// load tasks
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");
}
}
/**
* @功能名syncContent()
* @功能描述同步本地内容的方法。通过与GTask服务器进行通信将本地数据库中的笔记和任务列表与服务器上的数据进行同步
* @实现过程:
* 首先代码清空了mLocalDeleteIdMap这是一个用于存储已删除的笔记ID的HashSet。
* 接下来代码查询本地数据库中的已删除笔记并遍历结果集。对于每个已删除笔记代码从mGTaskHashMap中根据gid获取对应的Node对象。如果存在该Node对象说明该笔记在服务器上也存在需要进行远程删除操作。代码将该Node对象从mGTaskHashMap中移除并调用doContentSync方法执行同步操作。同时将该笔记的ID添加到mLocalDeleteIdMap中。
* 然后代码调用syncFolder方法同步文件夹信息。
* 接下来代码查询本地数据库中的已存在的笔记并遍历结果集。对于每个已存在的笔记代码从mGTaskHashMap中根据gid获取对应的Node对象。如果存在该Node对象说明该笔记在服务器上也存在需要根据Node对象的同步状态执行相应的同步操作。如果不存在该Node对象说明该笔记在服务器上不存在需要执行本地添加操作或远程删除操作。
* 最后代码使用Iterator迭代器遍历mGTaskHashMap中剩余的未处理的Node对象并执行本地添加操作。
* 在整个同步过程中代码会检查mCancelled变量是否被设置为true如果是则立即返回并结束同步过程。
* 最后代码会清空已删除的本地笔记并更新本地同步ID以及提交更新到GTask服务器。
* @Author:cyh
*/
private void syncContent() throws NetworkFailureException { //本地内容同步操作
int syncType;
Cursor c = null; //数据库指针
String gid;
Node node; //Node包含Sync_Action的不同类型
mLocalDeleteIdMap.clear(); //HashSet<Long>类型
if (mCancelled) {
return;
}
// for local deleted note
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;
}
}
// sync folder first
syncFolder();
// for note existing in database
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)); //通过hashmap建立联系
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid); //通过hashmap建立联系
syncType = node.getSyncAction(c);
} else {
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
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;
}
}
// go through remaining items
Iterator<Map.Entry<String, Node>> iter = mGTaskHashMap.entrySet().iterator(); //Iterator迭代器
while (iter.hasNext()) {
Map.Entry<String, Node> entry = iter.next();
node = entry.getValue();
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// mCancelled can be set by another thread, so we neet to check one by //thread----线程
// one
// clear local delete table
if (!mCancelled) {
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// refresh local sync id
if (!mCancelled) {
GTaskClient.getInstance().commitUpdate();
refreshLocalSyncId();
}
}
/**
* @功能名syncFolder()
* @功能描述:同步文件夹
* @实现过程首先查询root folder和 call-note folder 对应的 GTask 节点(通过 GTask ID分别进行同步操作如果节点不存在则添加节点到 GTask并更新 ContentProvider 中对应的值;如果节点存在则根据需要更新远程名称或者不做任何操作。接着,查询本地存在的文件夹,根据节点是否存在进行不同类型的同步操作,同步操作包括:添加、删除、更新本地或远程节点。最后,遍历所有在 GTask 中出现但未被同步的节点,将它们添加到 ContentProvider 中。
* @Author:cyh
*/
private void syncFolder() throws NetworkFailureException {
Cursor c = null;
String gid;
Node node;
int syncType;
if (mCancelled) {
return;
}
// for root folder
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);
// for system folder, only update remote name if necessary
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;
}
}
// for call-note folder
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);
// for system folder, only update remote name if
// necessary
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();
c = null;
}
}
// for local existing folders
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);
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) {
// local add
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
// remote delete
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;
}
}
// for remote add folders
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
gid = entry.getKey();
node = entry.getValue();
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
}
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
/**
* @功能名doContentSync(int syncType, Node node, Cursor c)
* @参数int syncType, Node node, Cursor c
* @功能描述:内容同步
* @实现过程在执行同步操作之前需要判断是否已经取消同步mCancelled如果已经取消同步则直接返回。在添加、删除、更新节点时需要同时更新 GTask 和 ContentProvider 中的数据,确保两者的数据一致性。此处还涉及到了 MetaData 和 mMetaHashMap用于保存所有节点在 GTask 中对应的元数据信息。
* @Author:cyh
*/
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));
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:
// merging both modifications maybe a good idea
// right now just use local update simply
updateRemoteNode(node, c);
break;
case Node.SYNC_ACTION_NONE:
break;
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
/**
* @功能名addLocalNode(Node node)
* @参数Node node
* @功能描述本地Node添加到 GTask 中
* @实现过程:
* 首先判断是否已经取消同步mCancelled如果已经取消同步则直接返回。
* 根据节点类型的不同,分别创建 SqlNote并将节点的内容设置到 SqlNote 中。
* 如果节点是 TaskList 类型,则需要判断节点的名称,并根据名称来设置 SqlNote 的内容。如果节点名称为默认文件夹,则创建一个根目录 SqlNote如果节点名称为通话记录文件夹则创建一个通话记录文件夹 SqlNote否则创建一个普通 SqlNote并将其内容设置为节点的本地 JSON 数据。
* 如果节点不是 TaskList 类型,则创建一个普通 SqlNote并将其内容设置为节点的本地 JSON 数据。同时,还需要设置该 SqlNote 的 parentId即节点的父节点在本地 ContentProvider 中对应的 Id。
* 设置 SqlNote 的 GTaskId 为节点的 Gid并将该 SqlNote 提交到本地 ContentProvider 中,创建一个本地节点。
* 更新 mGidToNid 和 mNidToGid用于保存 GTask 中的 Gid 和本地 ContentProvider 中的 Id 之间的映射关系。
* 最后,通过 updateRemoteMeta 方法,将该节点的元数据保存到 MetaData 中,确保 GTask 和本地 ContentProvider 中的节点信息一致。
* @Author:cyh
*/
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);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
sqlNote = new SqlNote(mContext);
sqlNote.setContent(node.getLocalJSONFromContent());
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
// the id is not available, have to create a new one
note.remove(NoteColumns.ID);
}
}
}
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
for (int i = 0; i < dataArray.length(); i++) {
JSONObject data = dataArray.getJSONObject(i);
if (data.has(DataColumns.ID)) {
long dataId = data.getLong(DataColumns.ID);
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
// the data id is not available, have to create
// a new one
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
Log.w(TAG, e.toString());
e.printStackTrace();
}
sqlNote.setContent(js);
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
sqlNote.setParentId(parentId.longValue());
}
// create the local node
sqlNote.setGtaskId(node.getGid());
sqlNote.commit(false);
// update gid-nid mapping
mGidToNid.put(node.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), node.getGid());
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
* @功能名updateLocalNode(Node node, Cursor c)
* @参数Node node, Cursor c
* @功能描述:将 GTask 中的节点信息更新到本地 ContentProvider 中。
* @实现过程:
* 首先判断是否已经取消同步mCancelled如果已经取消同步则直接返回。
* 根据 Cursor c 创建 SqlNote表示需要更新的本地节点。
* 将节点的本地 JSON 数据设置到 SqlNote 中,并根据节点类型的不同,设置 SqlNote 的 parentId。如果节点是 Task 类型,则根据节点的父节点在 GTask 中对应的 Gid 从 mGidToNid 中查找该节点在本地 ContentProvider 中对应的 Id并设置为 SqlNote 的 parentId否则设置 parentId 为根目录的 Id。
* 将 SqlNote 提交到本地 ContentProvider 中,完成节点的更新操作。
* 最后,通过 updateRemoteMeta 方法,将该节点的元数据保存到 MetaData 中,确保 GTask 和本地 ContentProvider 中的节点信息一致。
* @Author:cyh
*/
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote;
// update the note locally
sqlNote = new SqlNote(mContext, c);
sqlNote.setContent(node.getLocalJSONFromContent());
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");
throw new ActionFailureException("cannot update local node");
}
sqlNote.setParentId(parentId.longValue());
sqlNote.commit(true);
// update meta info
updateRemoteMeta(node.getGid(), sqlNote);
}
/**
* @功能名addRemoteNode(Node node, Cursor c)
* @参数Node node, Cursor c
* @功能描述:将本地的节点信息添加到远程服务器中。
* @实现过程:
* 首先判断是否已经取消同步mCancelled如果已经取消同步则直接返回。
* 根据 Cursor c 创建 SqlNote并根据该节点的类型进行不同的处理。
* 如果 SqlNote 是一个 Note 类型,表示需要添加的是一个笔记节点。首先根据 SqlNote 的内容创建一个 Task 对象,并将该 Task 对象添加到对应父节点在 GTask 中的子节点列表中。然后,使用 GTaskClient 实例的 createTask 方法,在远程服务器上创建该 Task。最后通过 updateRemoteMeta 方法,将该节点的元数据保存到 MetaData 中。
* 如果 SqlNote 不是一个 Note 类型,表示需要添加的是一个文件夹节点。根据 SqlNote 的 id 判断该文件夹节点的类型,并拼接出文件夹的名称。然后,遍历 mGTaskListHashMap查找是否存在名称与之匹配的文件夹节点如果存在则将 tasklist 设置为对应的 TaskList 对象,并删除 mGTaskHashMap 中对应的节点。如果不存在匹配的文件夹节点,则创建一个新的 TaskList 对象,并使用 GTaskClient 实例的 createTaskList 方法,在远程服务器上创建该 TaskList。
* 将节点的远程 Gid 更新到 SqlNote 中,并提交到本地 ContentProvider 中。同时更新 gid-id 和 id-gid 的映射关系。
* @Author:cyh
*/
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c); //从本地mContext中获取内容
Node n;
// update remotely
if (sqlNote.isNoteType()) {
Task task = new Task();
task.setContentByLocalJSON(sqlNote.getContent());
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist"); //调试信息
throw new ActionFailureException("cannot add remote task");
}
mGTaskListHashMap.get(parentGid).addChildTask(task); //在本地生成的GTaskList中增加子结点
//登录远程服务器创建Task
GTaskClient.getInstance().createTask(task);
n = (Node) task;
// add meta
updateRemoteMeta(task.getGid(), sqlNote);
} else {
TaskList tasklist = null;
// we need to skip folder if it has already existed
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迭代器通过统一的接口迭代所有的map元素
Iterator<Map.Entry<String, TaskList>> iter = mGTaskListHashMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, TaskList> entry = iter.next();
String gid = entry.getKey();
TaskList list = entry.getValue();
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// no match we can add now
if (tasklist == null) {
tasklist = new TaskList();
tasklist.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().createTaskList(tasklist);
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
n = (Node) tasklist;
}
// update local note
sqlNote.setGtaskId(n.getGid());
sqlNote.commit(false);
sqlNote.resetLocalModified();
sqlNote.commit(true);
// gid-id mapping //创建id间的映射
mGidToNid.put(n.getGid(), sqlNote.getId());
mNidToGid.put(sqlNote.getId(), n.getGid());
}
/**
* @功能名updateRemoteNode(Node node, Cursor c)
* @参数Node node, Cursor c
* @功能描述:更新远程服务器上的节点信息。
* @实现过程:
* 首先判断是否已经取消同步mCancelled如果已经取消同步则直接返回。
* 根据 Cursor c 创建 SqlNote并将其内容更新到节点对象 node 中。然后,使用 GTaskClient 实例的 addUpdateNode 方法,将更新后的节点信息同步到远程服务器。
* 通过 updateRemoteMeta 方法,更新节点的元数据。
* 接下来判断节点的类型是否为笔记Note。如果是笔记节点获取该节点的父节点列表 preParentList并根据 sqlNote 的 parentId 找到对应的父节点的 Gid curParentGid。如果 curParentGid 为 null则抛出异常。然后根据 curParentGid 在 mGTaskListHashMap 中找到对应的 TaskList 对象 curParentList。
* 如果 preParentList 和 curParentList 不相同,表示需要将节点移动到新的父节点列表中。首先从 preParentList 中移除该节点,然后将节点添加到 curParentList 中,并使用 GTaskClient 实例的 moveTask 方法,在远程服务器上移动该任务。
* @Author:cyh
*/
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
if (mCancelled) {
return;
}
SqlNote sqlNote = new SqlNote(mContext, c);
// update remotely
node.setContentByLocalJSON(sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(node);//GTaskClient用途为从本地登陆远端服务器
// update meta
updateRemoteMeta(node.getGid(), sqlNote);
// move task if necessary
if (sqlNote.isNoteType()) {
Task task = (Task) node;
TaskList preParentList = task.getParent();
//preParentList为通过node获取的父节点列表
String curParentGid = mNidToGid.get(sqlNote.getParentId());
//curParentGid为通过光标在数据库中找到sqlNote的mParentId再通过mNidToGid由long类型转为String类型的Gid
if (curParentGid == null) {
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
//通过HashMap找到对应Gid的TaskList
if (preParentList != curParentList) {
preParentList.removeChildTask(task);
curParentList.addChildTask(task);
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// clear local modified flag
sqlNote.resetLocalModified();
//commit到本地数据库
sqlNote.commit(true);
}
/**
* @功能名updateRemoteMeta(String gid, SqlNote sqlNote)
* @参数String gid, SqlNote sqlNote
* @功能描述:更新远程服务器上节点的元数据信息。
* @实现过程:
* 首先,判断参数 sqlNote 是否为笔记类型isNoteType并且不为 null。然后根据参数 gid 从 mMetaHashMap 中获取对应的元数据对象 metaData。
* 如果 metaData 不为 null则更新其内容为 sqlNote 的内容,并使用 GTaskClient 实例的 addUpdateNode 方法将更新后的元数据同步到远程服务器。
* 如果 metaData 为 null则创建一个新的 MetaData 对象,并设置其内容为 sqlNote 的内容。接着,将新创建的 metaData 添加到 mMetaList 中,同时将其放入 mMetaHashMap 中进行管理,最后使用 GTaskClient 实例的 createTask 方法,在远程服务器上创建该元数据节点。
* @Author:cyh
*/
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
if (sqlNote != null && sqlNote.isNoteType()) {
MetaData metaData = mMetaHashMap.get(gid);
if (metaData != null) {
metaData.setMeta(gid, sqlNote.getContent());
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
metaData = new MetaData();
metaData.setMeta(gid, sqlNote.getContent());
mMetaList.addChildTask(metaData);
mMetaHashMap.put(gid, metaData);
GTaskClient.getInstance().createTask(metaData);
}
}
}
/**
* @功能名refreshLocalSyncId()
* @功能描述刷新本地节点的同步ID
* @实现过程:
* 首先判断是否已经取消同步mCancelled如果已经取消同步则直接返回。
* 接着,清空 mGTaskHashMap、mGTaskListHashMap 和 mMetaHashMap然后初始化 GTaskList。
* 接下来,使用 ContentResolver 的 query 方法查询本地笔记数据,通过 Notes.CONTENT_NOTE_URI 和 SqlNote.PROJECTION_NOTE 获取到需要更新的笔记列表。
* 如果查询结果不为 null则遍历查询结果。对于每个查询结果获取其对应的 gid并从 mGTaskHashMap 中获取到对应的节点对象 node。
* 如果 node 不为 null表示该节点在远程服务器上存在需要更新同步ID。首先从 mGTaskHashMap 中移除该节点。然后,创建一个 ContentValues 对象 values并将需要更新的同步ID存入其中。
* 接着,使用 ContentResolver 的 update 方法,通过 ContentUris.withAppendedId 将更新的内容应用到对应的本地笔记记录上。
* 如果 node 为 null表示该本地笔记在远程服务器上没有对应的节点抛出 ActionFailureException 异常。
* 最后,在 finally 块中关闭游标 c。
* @Author:cyh
*/
private void refreshLocalSyncId() throws NetworkFailureException {
if (mCancelled) {
return;
}
// get the latest gtask list //获取最近的最晚的gtask list
mGTaskHashMap.clear();
mGTaskListHashMap.clear();
mMetaHashMap.clear();
initGTaskList();
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"); //query语句五个参数NoteColumns.TYPE + " DESC"-----为按类型递减顺序返回查询结果。new String[] {String.valueOf(Notes.TYPE_SYSTEM), String.valueOf(Notes.ID_TRASH_FOLER)}------为选择参数。"(type<>? AND parent_id<>?)"-------指明返回行过滤器。SqlNote.PROJECTION_NOTE--------应返回的数据列的名字。Notes.CONTENT_NOTE_URI--------contentProvider包含所有数据集所对应的uri
if (c != null) {
while (c.moveToNext()) {
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
Node node = mGTaskHashMap.get(gid);
if (node != null) {
mGTaskHashMap.remove(gid);
ContentValues values = new ContentValues(); //在ContentValues中创建键值对。准备通过contentResolver写入数据
values.put(NoteColumns.SYNC_ID, node.getLastModified());
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, //进行批量更改选择参数为NULL应该可以用insert替换参数分别为表名和需要更新的value对象。
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
Log.e(TAG, "something is missed");
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
if (c != null) {
c.close();
c = null;
}
}
}
/**
* @功能名getSyncAccount()
* @功能描述:获取当前同步账户的名称。
* @实现过程:调用 GTaskClient 实例的 getSyncAccount 方法获取当前的同步账户对象,然后返回其名称
* @Author:cyh
*/
public String getSyncAccount() {
return GTaskClient.getInstance().getSyncAccount().name;
}
/**
* @功能名cancelSync()
* @功能描述:取消同步操作
* @实现过程:将成员变量 mCancelled 的值设置为 true表示取消同步
* @Author:cyh
*/
public void cancelSync() {
mCancelled = true;
}
}