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.
rass/gtask/remote/GTaskManager.java

954 lines
66 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.
*/
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;
// 表示同步操作出现网络错误的状态码当在与Google Tasks服务通信过程中发生网络相关问题时返回该状态码。
public static final int STATE_NETWORK_ERROR = 1;
// 表示同步操作出现内部错误如数据处理、JSON解析等非网络方面的错误的状态码用于在同步流程内部逻辑出现问题时返回。
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;
// 单例模式下的唯一实例对象初始化为null通过静态方法getInstance获取实例确保整个应用中只有一个GTaskManager实例在运行。
private static GTaskManager mInstance = null;
// 存储当前相关的Activity对象用于获取认证令牌等与Activity上下文相关的操作可通过setActivityContext方法进行设置。
private Activity mActivity;
// 存储应用的上下文对象用于获取内容解析器ContentResolver等操作在同步操作等过程中频繁使用来访问本地数据库等资源。
private Context mContext;
// 用于操作本地内容提供器Content Provider的对象通过上下文获取负责对本地数据库进行查询、更新等操作例如查询本地笔记、文件夹等数据信息。
private ContentResolver mContentResolver;
// 标记当前是否正在进行同步操作初始化为false在同步操作开始时设置为true结束时设置为false用于避免并发同步以及判断同步状态。
private boolean mSyncing;
// 标记当前同步操作是否已被取消初始化为false当外部调用cancelSync方法时设置为true同步流程中会根据此标记来及时停止相关操作。
private boolean mCancelled;
// 用于存储从Google Tasks服务获取的任务列表TaskList类型信息以任务列表的唯一标识符Gid为键对应的TaskList对象为值方便快速查找和管理任务列表数据。
private HashMap<String, TaskList> mGTaskListHashMap;
// 用于存储从Google Tasks服务获取的各种节点Node类型包括任务、任务列表等信息以节点的唯一标识符Gid为键对应的Node对象为值用于统一管理和操作远程节点数据。
private HashMap<String, Node> mGTaskHashMap;
// 用于存储元数据MetaData类型信息以相关的唯一标识符可能与任务等的Gid关联为键对应的MetaData对象为值在同步过程中处理和维护元数据相关内容。
private HashMap<String, MetaData> mMetaHashMap;
// 存储元数据对应的任务列表对象,用于集中管理元数据相关的任务列表操作,例如加载元数据、添加元数据对应的任务等操作都围绕此对象进行。
private TaskList mMetaList;
// 用于记录本地已删除的笔记对应的ID集合在同步过程中标记哪些本地数据已被删除方便后续与远程数据进行对比和清理操作。
private HashSet<Long> mLocalDeleteIdMap;
// 用于建立远程节点的唯一标识符Gid与本地节点的IDNid通常对应本地数据库中的记录ID之间的映射关系方便在同步时查找和关联远程与本地的数据。
private HashMap<String, Long> mGidToNid;
// 与mGidToNid相反建立本地节点的IDNid与远程节点的唯一标识符Gid之间的映射关系同样用于同步操作中的数据关联和查找。
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>();
}
// 静态方法采用双重检查锁定Double-Checked Locking的方式实现单例模式确保多线程环境下能正确获取唯一的GTaskManager实例。
public static synchronized GTaskManager getInstance() {
if (mInstance == null) {
mInstance = new GTaskManager();
}
return mInstance;
}
// 设置当前相关的Activity上下文的方法主要用于后续获取认证令牌等操作外部在合适时机调用该方法传入对应的Activity对象来提供必要的上下文环境。
public synchronized void setActivityContext(Activity activity) {
// used for getting authtoken
mActivity = activity;
}
// 执行与Google Tasks服务的同步操作的核心方法首先判断是否已有同步操作在进行如果正在同步则直接返回相应状态码
// 然后初始化相关的成员变量、获取内容解析器对象、设置同步状态为正在进行以及取消状态为未取消等接着通过GTaskClient进行登录、初始化任务列表、同步内容等一系列操作
// 根据操作过程中出现的不同异常情况返回相应的状态码,最后在结束时清理相关的数据集合并重置同步状态,若同步被取消则返回取消状态码,否则返回成功状态码。
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 client = GTaskClient.getInstance();
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();
// do content sync work
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;
}
// 从Google Tasks服务初始化任务列表相关数据的方法首先判断同步是否已取消如果取消则直接返回
// 然后通过GTaskClient获取任务列表信息先处理元数据相关的任务列表如查找、创建元数据列表等再处理普通的任务列表加载任务列表及其包含的任务等
// 如果在JSON解析等过程中出现异常则抛出相应的异常表示初始化任务列表操作失败。
private void initGTaskList() throws NetworkFailureException {
if (mCancelled)
return;
GTaskClient client = GTaskClient.getInstance();
try {
JSONArray jsTaskLists = client.getTaskLists();
// init meta list first
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);
// 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()) {
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);
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);
// 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");
}
}
private void syncContent() throws NetworkFailureException {
// 用于记录当前节点的同步类型,根据不同情况(如本地新增、远程新增、删除等)来确定具体的值,以决定后续执行何种同步操作逻辑。
int syncType;
// 用于查询本地数据库的游标对象通过ContentResolver进行数据库查询操作在不同的查询场景下如查询已删除笔记、现有笔记等使用最后需要正确关闭以释放资源。
Cursor c = null;
// 用于存储节点如任务、任务列表等对应的唯一标识符Gid方便在不同数据结构如映射表、查询结果等中查找和关联相关节点数据。
String gid;
// 代表一个节点对象Node类型可表示任务、任务列表等在同步操作中对具体的节点进行各种处理如根据同步类型执行相应的添加、删除、更新操作等。
Node node;
// 清除本地已删除笔记的ID集合在每次进行内容同步开始时先清空之前记录的已删除笔记相关信息准备重新统计和处理本次同步中的删除情况。
mLocalDeleteIdMap.clear();
// 如果同步操作已被取消通过mCancelled标志判断则直接返回不再执行后续的同步内容相关逻辑。
if (mCancelled) {
return;
}
// 处理本地已删除笔记的同步逻辑
try {
// 通过ContentResolver查询本地数据库中处于回收站Notes.ID_TRASH_FOLER之外且非系统类型Notes.TYPE_SYSTEM的笔记信息获取用于后续判断和处理的数据游标。
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);
// 根据Gid从存储远程节点的映射表mGTaskHashMap中查找对应的节点对象
node = mGTaskHashMap.get(gid);
if (node!= null) {
// 如果找到了对应的远程节点,说明该笔记在远程端也存在,先从映射表中移除该节点(可能后续会根据同步情况重新添加或更新)
mGTaskHashMap.remove(gid);
// 执行针对远程删除的同步操作调用doContentSync方法并传入相应的同步类型Node.SYNC_ACTION_DEL_REMOTE以及当前节点和游标对象等参数。
doContentSync(Node.SYNC_ACTION_DEL_REMOTE, node, c);
}
// 将当前本地已删除笔记的ID添加到本地删除笔记ID集合中用于后续统一清理本地相关记录等操作。
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
}
} else {
// 如果查询游标为null说明查询本地回收站笔记信息失败记录相应的警告日志。
Log.w(TAG, "failed to query trash folder");
}
} finally {
// 无论查询操作是否成功,都要确保游标对象被正确关闭,释放相关资源,避免内存泄漏等问题。
if (c!= null) {
c.close();
c = null;
}
}
// 先执行文件夹的同步操作调用syncFolder方法来处理文件夹相关的同步逻辑包括本地文件夹与远程文件夹的对比、添加、更新等情况。
syncFolder();
// 处理本地数据库中现有笔记非回收站中的笔记且类型为普通笔记Notes.TYPE_NOTE的同步逻辑
try {
// 通过ContentResolver查询符合条件的笔记信息获取游标对象用于后续遍历处理。
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);
// 根据Gid查找对应的远程节点对象
node = mGTaskHashMap.get(gid);
if (node!= null) {
// 如果找到了对应的远程节点,从映射表中移除该节点(后续可能重新添加或更新)
mGTaskHashMap.remove(gid);
// 将远程节点的Gid与本地笔记的ID建立映射关系方便后续操作中相互查找和关联。
mGidToNid.put(gid, c.getLong(SqlNote.ID_COLUMN));
mNidToGid.put(c.getLong(SqlNote.ID_COLUMN), gid);
// 获取当前节点对应的同步类型通过调用节点的getSyncAction方法并传入游标对象来确定是新增、更新等哪种情况。
syncType = node.getSyncAction(c);
} else {
// 如果未找到对应的远程节点根据本地笔记的Gid情况判断同步类型如果Gid为空字符串说明是本地新增的笔记同步类型设为Node.SYNC_ACTION_ADD_REMOTE如果Gid不为空则认为是远程已删除但本地还存在的情况同步类型设为Node.SYNC_ACTION_DEL_LOCAL。
if (c.getString(SqlNote.GTASK_ID_COLUMN).trim().length() == 0) {
syncType = Node.SYNC_ACTION_ADD_REMOTE;
} else {
syncType = Node.SYNC_ACTION_DEL_LOCAL;
}
}
// 根据确定的同步类型执行相应的内容同步操作调用doContentSync方法传入同步类型、节点对象和游标对象等参数。
doContentSync(syncType, node, c);
}
} else {
// 如果查询游标为null记录查询现有笔记失败的警告日志。
Log.w(TAG, "failed to query existing note in database");
}
} finally {
// 确保游标对象被正确关闭,释放资源。
if (c!= null) {
c.close();
c = null;
}
}
// 处理剩余的远程节点即经过前面的处理后仍留在mGTaskHashMap中的节点可能是远程新增但本地还未处理的情况
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和节点对象等参数。
doContentSync(Node.SYNC_ACTION_ADD_LOCAL, node, null);
}
// 由于mCancelled可能被其他线程设置例如外部调用了取消同步的方法所以需要逐个检查确认是否已取消同步这里检查如果未取消同步
if (!mCancelled) {
// 尝试批量删除本地已记录的删除笔记,如果批量删除操作失败,抛出相应的异常表示无法批量删除本地已删除的笔记。
if (!DataUtils.batchDeleteNotes(mContentResolver, mLocalDeleteIdMap)) {
throw new ActionFailureException("failed to batch-delete local deleted notes");
}
}
// 如果同步未被取消
if (!mCancelled) {
// 提交之前暂存的更新操作通过GTaskClient的commitUpdate方法确保之前积累的对远程数据的更新请求如添加、修改等操作发送到服务器端进行处理。
GTaskClient.getInstance().commitUpdate();
// 刷新本地同步ID调用refreshLocalSyncId方法使本地数据的同步标识与远程数据的最新状态保持一致便于后续判断数据是否需要再次同步等情况。
refreshLocalSyncId();
}
}
private void syncFolder() throws NetworkFailureException {
// 用于查询本地数据库的游标对象,在不同的文件夹查询场景下使用,最后需要正确关闭以释放资源。
Cursor c = null;
// 用于存储文件夹对应的唯一标识符Gid方便在数据结构中查找和关联相关文件夹数据。
String gid;
// 代表一个节点对象Node类型这里主要用于表示文件夹节点对文件夹节点进行各种同步相关的处理如添加、更新等操作。
Node node;
// 用于记录文件夹的同步类型,根据文件夹在本地和远程的存在情况、名称变化等确定具体的同步操作类型(如新增、更新等)。
int syncType;
// 如果同步操作已被取消通过mCancelled标志判断则直接返回不再执行后续的文件夹同步逻辑。
if (mCancelled) {
return;
}
// 处理根文件夹的同步逻辑
try {
// 通过ContentResolver查询本地数据库中根文件夹Notes.ID_ROOT_FOLDER的信息获取游标对象用于后续操作。
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);
// 根据Gid从存储远程节点的映射表mGTaskHashMap中查找对应的节点对象代表根文件夹的远程节点
node = mGTaskHashMap.get(gid);
if (node!= null) {
// 如果找到了对应的远程根文件夹节点,从映射表中移除该节点(后续可能根据情况重新添加或更新)
mGTaskHashMap.remove(gid);
// 建立根文件夹的远程Gid与本地IDNotes.ID_ROOT_FOLDER的映射关系方便后续操作中相互查找和关联。
mGidToNid.put(gid, (long) Notes.ID_ROOT_FOLDER);
mNidToGid.put((long) Notes.ID_ROOT_FOLDER, gid);
// 对于系统文件夹这里的根文件夹属于系统文件夹仅在远程名称与本地预期名称不一致时执行远程更新操作调用doContentSync方法并传入相应的同步类型Node.SYNC_ACTION_UPDATE_REMOTE以及当前节点和游标对象等参数。
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以及当前节点这里为null因为远程不存在该节点和游标对象等参数。
doContentSync(Node.SYNC_ACTION_ADD_REMOTE, node, c);
}
} else {
// 如果查询游标为null说明查询根文件夹信息失败记录相应的警告日志。
Log.w(TAG, "failed to query root folder");
}
} finally {
// 无论查询操作是否成功,都要确保游标对象被正确关闭,释放相关资源,避免内存泄漏等问题。
if (c!= null) {
c.close();
c = null;
}
}
// 处理通话记录文件夹Notes.ID_CALL_RECORD_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);
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;
}
}
// 处理本地现有文件夹非回收站中的文件夹且类型为文件夹Notes.TYPE_FOLDER的同步逻辑通过查询获取游标对象并遍历处理每一个文件夹记录。
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) {
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;
}
}
// 处理远程新增的文件夹即本地不存在但远程有通过对比本地存储的任务列表映射表mGTaskListHashMap和节点映射表mGTaskHashMap来确定
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);
}
}
// 如果同步未被取消提交之前暂存的更新操作通过GTaskClient的commitUpdate方法确保与文件夹相关的更新操作能发送到服务器端进行处理。
if (!mCancelled)
GTaskClient.getInstance().commitUpdate();
}
private void doContentSync(int syncType, Node node, Cursor c) throws NetworkFailureException {
// 如果同步操作已被取消通过检查mCancelled标志则直接返回不执行后续的同步操作逻辑。
if (mCancelled) {
return;
}
MetaData meta;
switch (syncType) {
// 同步类型为本地添加Node.SYNC_ACTION_ADD_LOCAL的情况
case Node.SYNC_ACTION_ADD_LOCAL:
// 调用addLocalNode方法来处理在本地添加节点的相关逻辑比如创建本地记录、更新相关映射关系等操作。
addLocalNode(node);
break;
// 同步类型为远程添加Node.SYNC_ACTION_ADD_REMOTE的情况
case Node.SYNC_ACTION_ADD_REMOTE:
// 调用addRemoteNode方法来处理向远程如Google Tasks服务添加节点的相关逻辑同时传入当前节点和游标对象用于获取相关数据辅助操作比如创建任务、任务列表等远程操作以及相应的本地记录更新。
addRemoteNode(node, c);
break;
// 同步类型为本地删除Node.SYNC_ACTION_DEL_LOCAL的情况
case Node.SYNC_ACTION_DEL_LOCAL:
// 从元数据映射表mMetaHashMap中根据当前节点对应的Gid从游标获取的对应列值获取对应的元数据对象。
meta = mMetaHashMap.get(c.getString(SqlNote.GTASK_ID_COLUMN));
if (meta!= null) {
// 如果获取到了对应的元数据对象调用GTaskClient的deleteNode方法删除该元数据节点以保持远程数据与本地删除操作的一致性。
GTaskClient.getInstance().deleteNode(meta);
}
// 将当前本地要删除的节点的ID添加到本地删除笔记ID集合mLocalDeleteIdMap用于后续统一清理本地相关记录等操作。
mLocalDeleteIdMap.add(c.getLong(SqlNote.ID_COLUMN));
break;
// 同步类型为远程删除Node.SYNC_ACTION_DEL_REMOTE的情况
case Node.SYNC_ACTION_DEL_REMOTE:
// 从元数据映射表mMetaHashMap中根据当前节点的Gid获取对应的元数据对象。
meta = mMetaHashMap.get(node.getGid());
if (meta!= null) {
// 如果对应的元数据对象存在调用GTaskClient的deleteNode方法删除该元数据节点。
GTaskClient.getInstance().deleteNode(meta);
}
// 调用GTaskClient的deleteNode方法删除当前要处理的远程节点本身实现远程数据的删除操作。
GTaskClient.getInstance().deleteNode(node);
break;
// 同步类型为本地更新Node.SYNC_ACTION_UPDATE_LOCAL的情况
case Node.SYNC_ACTION_UPDATE_LOCAL:
// 调用updateLocalNode方法来处理在本地更新节点相关信息的逻辑比如更新本地数据库中的记录内容、相关标识等操作。
updateLocalNode(node, c);
break;
// 同步类型为远程更新Node.SYNC_ACTION_UPDATE_REMOTE的情况
case Node.SYNC_ACTION_UPDATE_REMOTE:
// 调用updateRemoteNode方法来处理向远程如Google Tasks服务更新节点相关信息的逻辑比如更新远程任务、任务列表的内容等操作同时传入当前节点和游标对象用于获取相关数据辅助更新。
updateRemoteNode(node, c);
break;
// 同步类型为更新冲突Node.SYNC_ACTION_UPDATE_CONFLICT的情况
case Node.SYNC_ACTION_UPDATE_CONFLICT:
// 这里注释提到合并双方修改可能是个好主意,但目前只是简单采用本地更新的方式来处理。
// 调用updateRemoteNode方法按照本地更新的方式来处理这种冲突情况向远程更新节点信息传入当前节点和游标对象辅助操作。
updateRemoteNode(node, c);
break;
// 同步类型为无操作Node.SYNC_ACTION_NONE的情况直接跳过不执行任何操作。
case Node.SYNC_ACTION_NONE:
break;
// 同步类型为错误Node.SYNC_ACTION_ERROR或其他未定义的情况抛出异常表示遇到了未知的同步操作类型。
case Node.SYNC_ACTION_ERROR:
default:
throw new ActionFailureException("unkown sync action type");
}
}
private void addLocalNode(Node node) throws NetworkFailureException {
// 如果同步操作已被取消通过检查mCancelled标志则直接返回不执行后续添加本地节点的逻辑。
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 判断当前节点是否是任务列表TaskList类型的实例
if (node instanceof TaskList) {
// 如果是根文件夹(通过名称判断是否匹配特定的根文件夹标识)
if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT)) {
// 创建一个SqlNote对象关联到本地的根文件夹传入Notes.ID_ROOT_FOLDER作为参数用于后续操作本地数据库相关记录。
sqlNote = new SqlNote(mContext, Notes.ID_ROOT_FOLDER);
} else if (node.getName().equals(
GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE)) {
// 如果是通话记录文件夹通过名称判断创建一个SqlNote对象关联到本地的通话记录文件夹传入Notes.ID_CALL_RECORD_FOLDER作为参数
sqlNote = new SqlNote(mContext, Notes.ID_CALL_RECORD_FOLDER);
} else {
// 如果是其他普通的任务列表创建一个新的SqlNote对象用于后续设置相关内容并插入到本地数据库。
sqlNote = new SqlNote(mContext);
// 设置SqlNote对象的内容为从当前节点获取的本地JSON格式内容通过节点的getLocalJSONFromContent方法获取该内容包含了任务列表的相关详细信息。
sqlNote.setContent(node.getLocalJSONFromContent());
// 设置父文件夹ID为根文件夹IDNotes.ID_ROOT_FOLDER表示该任务列表在本地的层级关系。
sqlNote.setParentId(Notes.ID_ROOT_FOLDER);
}
} else {
// 如果当前节点不是任务列表而是普通任务Task类型等其他节点情况创建一个新的SqlNote对象用于后续操作。
sqlNote = new SqlNote(mContext);
JSONObject js = node.getLocalJSONFromContent();
try {
// 如果从节点获取的本地JSON内容中包含特定的元数据头部笔记标识GTaskStringUtils.META_HEAD_NOTE
if (js.has(GTaskStringUtils.META_HEAD_NOTE)) {
// 获取对应的笔记JSON对象。
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
if (note.has(NoteColumns.ID)) {
long id = note.getLong(NoteColumns.ID);
// 检查该笔记ID在本地笔记数据库中是否已存在通过DataUtils的existInNoteDatabase方法如果已存在表示该ID不可用需要移除这个ID字段可能重新生成新的ID等情况
if (DataUtils.existInNoteDatabase(mContentResolver, id)) {
note.remove(NoteColumns.ID);
}
}
}
// 如果从节点获取的本地JSON内容中包含特定的元数据头部数据标识GTaskStringUtils.META_HEAD_DATA
if (js.has(GTaskStringUtils.META_HEAD_DATA)) {
// 获取对应的JSON数组里面包含了多个数据对象。
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);
// 检查该数据ID在本地数据数据库中是否已存在通过DataUtils的existInDataDatabase方法如果已存在移除这个ID字段可能重新生成等情况
if (DataUtils.existInDataDatabase(mContentResolver, dataId)) {
data.remove(DataColumns.ID);
}
}
}
}
} catch (JSONException e) {
// 如果在JSON解析等操作过程中出现异常记录相应的警告日志并打印异常堆栈信息。
Log.w(TAG, e.toString());
e.printStackTrace();
}
// 设置SqlNote对象的内容为处理后的JSON对象包含了任务相关的正确数据信息。
sqlNote.setContent(js);
// 获取当前任务节点的父节点的Gid对应的本地节点ID通过查找mGidToNid映射表来获取用于确定该任务在本地的正确父级关系。
Long parentId = mGidToNid.get(((Task) node).getParent().getGid());
if (parentId == null) {
// 如果找不到对应的父节点ID记录错误日志并抛出异常表示无法添加本地节点因为缺少父节点关联信息。
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot add local node");
}
// 设置SqlNote对象的父节点ID为获取到的父节点ID对应的长整型值建立本地任务节点与父节点的关联关系。
sqlNote.setParentId(parentId.longValue());
}
// 设置SqlNote对象对应的远程节点的Gid即当前要添加的本地节点对应的远程唯一标识符用于后续关联本地与远程数据。
sqlNote.setGtaskId(node.getGid());
// 将SqlNote对象插入到本地数据库中commit方法传入false可能表示以特定的插入模式比如非自动更新相关关联数据等情况具体看SqlNote类的实现进行插入操作。
sqlNote.commit(false);
// 在远程Gid与本地ID的映射表mGidToNid中添加一条记录将当前节点的Gid与刚插入本地数据库的SqlNote对象的ID建立映射关系方便后续查找和同步操作使用。
mGidToNid.put(node.getGid(), sqlNote.getId());
// 在本地ID与远程Gid的映射表mNidToGid中添加反向的映射关系即本地ID对应远程Gid保持双向关联。
mNidToGid.put(sqlNote.getId(), node.getGid());
// 调用updateRemoteMeta方法根据当前节点的Gid和SqlNote对象来更新远程元数据相关信息保持数据的一致性和完整性。
updateRemoteMeta(node.getGid(), sqlNote);
}
private void updateLocalNode(Node node, Cursor c) throws NetworkFailureException {
// 如果同步操作已被取消通过检查mCancelled标志则直接返回不执行后续更新本地节点的逻辑。
if (mCancelled) {
return;
}
SqlNote sqlNote;
// 更新本地的笔记信息创建一个SqlNote对象通过传入上下文和游标对象来初始化该对象用于操作本地数据库中的对应记录。
sqlNote = new SqlNote(mContext, c);
// 设置SqlNote对象的内容为从当前节点获取的本地JSON格式内容通过节点的getLocalJSONFromContent方法获取用于更新本地数据库中记录的具体内容。
sqlNote.setContent(node.getLocalJSONFromContent());
Long parentId = (node instanceof Task)? mGidToNid.get(((Task) node).getParent().getGid())
: new Long(Notes.ID_ROOT_FOLDER);
if (parentId == null) {
// 如果获取不到对应的父节点ID对于任务节点通过查找映射表获取父节点Gid对应的本地ID对于非任务节点默认设为根文件夹ID如果还是获取不到则为null记录错误日志并抛出异常表示无法更新本地节点因为缺少父节点关联信息。
Log.e(TAG, "cannot find task's parent id locally");
throw new ActionFailureException("cannot update local node");
}
// 设置SqlNote对象的父节点ID为获取到的有效父节点ID对应的长整型值建立本地节点与正确父节点的关联关系确保更新操作在正确的层级下进行。
sqlNote.setParentId(parentId.longValue());
// 将更新后的SqlNote对象提交到本地数据库commit方法传入true可能表示以另一种更新模式比如自动更新相关关联数据等情况具体看SqlNote类的实现进行更新操作使本地数据库中的记录与当前节点的最新信息保持一致。
sqlNote.commit(true);
// 调用updateRemoteMeta方法根据当前节点的Gid和更新后的SqlNote对象来更新远程元数据相关信息保持本地与远程数据的同步和一致性。
updateRemoteMeta(node.getGid(), sqlNote);
}
private void addRemoteNode(Node node, Cursor c) throws NetworkFailureException {
// 首先判断同步操作是否已被取消,如果已取消则直接返回,不执行后续添加远程节点的逻辑。
if (mCancelled) {
return;
}
// 创建一个SqlNote对象通过传入当前上下文mContext和游标c来初始化该对象用于后续对本地数据库中相关记录进行操作比如获取记录信息、更新记录状态等。
SqlNote sqlNote = new SqlNote(mContext, c);
Node n;
// 判断当前SqlNote所代表的是否是笔记类型通过isNoteType方法判断如果是笔记类型则执行向远程添加任务节点的相关逻辑。
if (sqlNote.isNoteType()) {
// 创建一个新的Task任务对象用于表示要添加到远程的任务。
Task task = new Task();
// 设置任务对象的内容通过调用setContentByLocalJSON方法并传入从SqlNote对象获取的本地JSON格式内容将本地存储的任务相关信息传递给该任务对象以便后续发送到远程进行创建操作。
task.setContentByLocalJSON(sqlNote.getContent());
// 尝试获取该任务的父任务列表对应的远程唯一标识符Gid通过查找本地ID与远程Gid的映射表mNidToGid以SqlNote对象中记录的父节点ID为键来获取对应的值即父任务列表的Gid
String parentGid = mNidToGid.get(sqlNote.getParentId());
if (parentGid == null) {
// 如果无法获取到父任务列表的Gid说明缺少必要的关联信息记录错误日志并抛出异常表示无法添加远程任务因为找不到对应的父任务列表。
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot add remote task");
}
// 根据获取到的父任务列表的Gid从存储远程任务列表的映射表mGTaskListHashMap中获取对应的任务列表对象并调用其addChildTask方法将新创建的任务添加到该父任务列表中建立任务的层级关系。
mGTaskListHashMap.get(parentGid).addChildTask(task);
// 调用GTaskClient的createTask方法将创建好的任务对象发送到远程例如Google Tasks服务进行创建操作实现向远程添加任务节点的功能。
GTaskClient.getInstance().createTask(task);
// 将创建好的远程任务节点以Node类型表示这里通过强制类型转换将Task类型转换为Node类型赋值给n方便后续统一处理新添加的远程节点相关操作。
n = (Node) task;
// 调用updateRemoteMeta方法传入刚创建的任务的Gid以及对应的SqlNote对象用于更新与该任务相关的远程元数据信息确保远程数据的完整性和一致性比如记录任务相关的额外描述、属性等元数据信息。
updateRemoteMeta(task.getGid(), sqlNote);
} else {
// 如果不是笔记类型说明要处理的是任务列表文件夹相关的添加操作先初始化任务列表对象为null后续根据情况查找或创建相应的任务列表。
TaskList tasklist = null;
// 构建文件夹名称先添加特定的前缀GTaskStringUtils.MIUI_FOLDER_PREFFIX然后根据SqlNote对象的ID情况添加不同的后缀来确定具体的文件夹名称。
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();
}
// 通过迭代器遍历存储远程任务列表的映射表mGTaskListHashMap中的所有元素以键值对形式键为任务列表的Gid值为任务列表对象用于查找是否已存在同名的任务列表。
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();
// 如果找到名称与构建的folderName相同的任务列表说明该文件夹在远程已存在将其赋值给tasklist并判断是否在另一个存储远程节点的映射表mGTaskHashMap中存在该任务列表对应的节点如果存在则移除可能后续会重新添加或更新相关信息然后跳出循环。
if (list.getName().equals(folderName)) {
tasklist = list;
if (mGTaskHashMap.containsKey(gid)) {
mGTaskHashMap.remove(gid);
}
break;
}
}
// 如果经过上述查找后tasklist仍为null说明没有找到同名的任务列表即该文件夹在远程不存在需要创建新的任务列表。
if (tasklist == null) {
// 创建一个新的TaskList任务列表对象用于表示要添加到远程的任务列表文件夹
tasklist = new TaskList();
// 设置任务列表对象的内容通过调用setContentByLocalJSON方法并传入从SqlNote对象获取的本地JSON格式内容将本地存储的任务列表相关信息传递给该对象以便后续发送到远程进行创建操作。
tasklist.setContentByLocalJSON(sqlNote.getContent());
// 调用GTaskClient的createTaskList方法将创建好的任务列表对象发送到远程例如Google Tasks服务进行创建操作实现向远程添加任务列表文件夹的功能。
GTaskClient.getInstance().createTaskList(tasklist);
// 将新创建的任务列表添加到存储远程任务列表的映射表mGTaskListHashMap以其Gid为键任务列表对象本身为值方便后续查找和管理远程任务列表数据。
mGTaskListHashMap.put(tasklist.getGid(), tasklist);
}
// 将创建好的远程任务列表节点以Node类型表示这里通过强制类型转换将TaskList类型转换为Node类型赋值给n方便后续统一处理新添加的远程节点相关操作。
n = (Node) tasklist;
}
// 更新本地SqlNote对象对应的远程节点的Gid将其设置为新添加或找到的远程节点无论是任务还是任务列表转换后的Node类型节点的Gid以此建立本地记录与远程节点的关联关系便于后续同步操作中进行对应查找等操作。
sqlNote.setGtaskId(n.getGid());
// 将SqlNote对象提交到本地数据库传入false可能表示以特定的提交模式例如不自动触发某些关联数据的更新等情况具体取决于SqlNote类的实现逻辑进行提交操作可能执行插入或更新本地数据库记录的相关操作使本地数据库中的记录与远程操作结果保持一定的一致性。
sqlNote.commit(false);
// 重置SqlNote对象的本地修改标志例如可能用于标记该记录在本地是否有过修改等情况具体功能看其在类中的定义将其恢复到初始或未修改的状态可能是为了后续准确判断记录的修改情况等操作。
sqlNote.resetLocalModified();
// 再次将SqlNote对象提交到本地数据库传入true可能表示以另一种提交模式例如会自动更新相关联的数据等情况同样取决于SqlNote类的实现逻辑进行提交操作确保本地数据库中的记录状态完全更新与远程操作及相关处理后的结果准确匹配。
sqlNote.commit(true);
// 在远程Gid与本地ID的映射表mGidToNid中添加一条映射记录将新添加的远程节点的Gid作为键对应的本地SqlNote对象的ID作为值方便后续在同步过程中通过远程Gid快速查找本地对应的记录ID用于数据关联和操作。
mGidToNid.put(n.getGid(), sqlNote.getId());
// 在本地ID与远程Gid的映射表mNidToGid中添加反向的映射记录即将本地SqlNote对象的ID作为键对应的新添加的远程节点的Gid作为值建立双向的映射关系便于在不同的操作场景下进行本地与远程数据的相互查找和关联。
mNidToGid.put(sqlNote.getId(), n.getGid());
}
private void updateRemoteNode(Node node, Cursor c) throws NetworkFailureException {
// 首先判断同步操作是否已被取消,如果已取消则直接返回,不执行后续更新远程节点的逻辑。
if (mCancelled) {
return;
}
// 创建一个SqlNote对象通过传入当前上下文mContext和游标c来初始化该对象用于获取本地数据库中对应记录的相关信息以辅助更新远程节点的操作。
SqlNote sqlNote = new SqlNote(mContext, c);
// 更新远程节点的内容通过调用节点node的setContentByLocalJSON方法并传入从SqlNote对象获取的本地JSON格式内容将本地存储的最新节点信息传递给远程节点实现远程节点内容的更新使其与本地记录保持一致。
node.setContentByLocalJSON(sqlNote.getContent());
// 调用GTaskClient的addUpdateNode方法将更新后的节点对象发送到远程例如Google Tasks服务通知远程服务对相应的节点进行更新操作具体的更新实现取决于远程服务端的逻辑。
GTaskClient.getInstance().addUpdateNode(node);
// 调用updateRemoteMeta方法传入当前节点的Gid以及对应的SqlNote对象用于更新与该节点相关的远程元数据信息确保远程数据的完整性和一致性因为节点内容更新后可能对应的元数据也需要相应调整。
updateRemoteMeta(node.getGid(), sqlNote);
// 判断当前SqlNote对象是否代表笔记类型即是否是任务节点如果是则执行与任务移动相关的逻辑可能涉及到任务在不同任务列表之间切换等情况
if (sqlNote.isNoteType()) {
// 将当前节点强制转换为Task类型方便后续操作任务相关的属性比如获取父任务列表等信息。
Task task = (Task) node;
// 获取任务当前所在的父任务列表对象,用于后续对比和判断是否需要移动任务到其他任务列表。
TaskList preParentList = task.getParent();
// 尝试获取任务的目标父任务列表对应的远程唯一标识符Gid通过查找本地ID与远程Gid的映射表mNidToGid以SqlNote对象中记录的父节点ID为键来获取对应的值即目标父任务列表的Gid
String curParentGid = mNidToGid.get(sqlNote.getParentId());
if (curParentGid == null) {
// 如果无法获取到目标父任务列表的Gid说明缺少必要的关联信息记录错误日志并抛出异常表示无法更新远程任务因为找不到对应的目标父任务列表。
Log.e(TAG, "cannot find task's parent tasklist");
throw new ActionFailureException("cannot update remote task");
}
// 根据获取到的目标父任务列表的Gid从存储远程任务列表的映射表mGTaskListHashMap中获取对应的任务列表对象用于后续操作任务在不同任务列表之间的移动。
TaskList curParentList = mGTaskListHashMap.get(curParentGid);
// 判断当前任务的原父任务列表和目标父任务列表是否不同,如果不同,则说明需要将任务从原父任务列表移动到目标父任务列表。
if (preParentList!= curParentList) {
// 从原父任务列表中移除当前任务调用原父任务列表的removeChildTask方法将任务从原层级关系中移除。
preParentList.removeChildTask(task);
// 将当前任务添加到目标父任务列表中调用目标父任务列表的addChildTask方法建立任务在新的目标父任务列表中的层级关系完成任务的移动操作。
curParentList.addChildTask(task);
// 调用GTaskClient的moveTask方法通知远程服务例如Google Tasks服务执行任务的移动操作将任务从原父任务列表移动到目标父任务列表确保远程数据的任务层级关系与本地操作结果一致。
GTaskClient.getInstance().moveTask(task, preParentList, curParentList);
}
}
// 清除SqlNote对象的本地修改标志例如可能用于标记该记录在本地是否有过修改等情况具体功能看其在类中的定义将其恢复到初始或未修改的状态可能是为了后续准确判断记录的修改情况等操作以及与远程数据保持一致的状态标识。
sqlNote.resetLocalModified();
// 将SqlNote对象再次提交到本地数据库传入true可能表示以特定的提交模式例如会自动更新相关联的数据等情况具体取决于SqlNote类的实现逻辑进行提交操作确保本地数据库中的记录状态与远程节点更新后的情况准确匹配保持数据的一致性。
sqlNote.commit(true);
}
private void updateRemoteMeta(String gid, SqlNote sqlNote) throws NetworkFailureException {
// 首先判断传入的SqlNote对象是否为空并且是否代表笔记类型通过isNoteType方法判断只有满足这两个条件才执行后续更新远程元数据的逻辑可能是因为只有笔记类型的节点才有对应的元数据需要处理且需要有效的SqlNote对象来获取相关内容信息。
if (sqlNote!= null && sqlNote.isNoteType()) {
// 尝试从存储元数据的映射表mMetaHashMap以传入的节点的Gid为键获取对应的元数据对象MetaData类型用于后续判断该元数据是否已存在以及相应的更新操作。
MetaData metaData = mMetaHashMap.get(gid);
if (metaData!= null) {
// 如果获取到了对应的元数据对象说明该元数据已存在调用其setMeta方法传入节点的Gid以及从SqlNote对象获取的内容信息用于更新该元数据对象的具体内容使其与本地最新的节点信息保持一致比如更新任务相关的额外描述、属性等元数据信息。
metaData.setMeta(gid, sqlNote.getContent());
// 调用GTaskClient的addUpdateNode方法将更新后的元数据对象发送到远程例如Google Tasks服务通知远程服务对相应的元数据进行更新操作确保远程元数据与本地数据的一致性。
GTaskClient.getInstance().addUpdateNode(metaData);
} else {
// 如果没有获取到对应的元数据对象,说明该元数据不存在,需要创建新的元数据对象。
metaData = new MetaData();
// 调用新创建的元数据对象的setMeta方法传入节点的Gid以及从SqlNote对象获取的内容信息用于初始化该元数据对象的内容使其包含与当前节点相关的必要信息。
metaData.setMeta(gid, sqlNote.getContent());
// 将新创建的元数据对象添加到存储元数据的任务列表mMetaList作为其子任务可能是一种组织和管理元数据的方式具体看相关类的实现逻辑建立元数据与任务列表的关联关系。
mMetaList.addChildTask(metaData);
// 将新创建的元数据对象添加到存储元数据的映射表mMetaHashMap以节点的Gid为键元数据对象本身为值方便后续查找和管理元数据信息确保元数据与对应节点的关联关系在整个同步过程中可维护。
mMetaHashMap.put(gid, metaData);
// 调用GTaskClient的createTask方法将新创建的元数据对象发送到远程例如Google Tasks服务进行创建操作实现向远程添加新的元数据信息的功能确保远程数据中包含最新的元数据内容与本地数据匹配。
GTaskClient.getInstance().createTask(metaData);
}
}
}
private void refreshLocalSyncId() throws NetworkFailureException {
// 首先判断同步操作是否已经被取消,如果 `mCancelled` 标志为 `true`,则直接返回,不执行后续刷新本地同步 ID 的相关逻辑。
if (mCancelled) {
return;
}
// 获取最新的 gtask 列表相关操作,以下几步是为了清除之前可能缓存的相关数据结构中的信息,准备重新获取最新的数据进行后续处理。
// 清空存储远程节点信息的映射表 `mGTaskHashMap`,清除之前记录的远程节点相关数据,避免旧数据影响后续基于最新数据的操作。
mGTaskHashMap.clear();
// 清空存储远程任务列表信息的映射表 `mGTaskListHashMap`,同样是为了清理旧的任务列表相关缓存数据。
mGTaskListHashMap.clear();
// 清空存储元数据信息的映射表 `mMetaHashMap`,确保后续元数据操作基于最新获取的数据。
mMetaHashMap.clear();
// 调用 `initGTaskList` 方法,该方法的作用应该是从远程数据源(比如可能是和某个远程任务服务交互)获取最新的任务列表相关数据,并对相关数据结构进行初始化填充,为后续刷新本地同步 ID 的操作准备好基础数据。
initGTaskList();
// 定义一个游标对象 `c`,用于后续查询本地数据库操作,初始化为 `null`,在使用前需要正确初始化并在最后确保关闭以释放资源。
Cursor c = null;
try {
// 通过 `ContentResolver` 发起对本地数据库的查询操作,查询的是 `Notes.CONTENT_NOTE_URI` 所指向的内容(通常表示笔记相关的数据表)。
// 查询所需要获取的列信息由 `SqlNote.PROJECTION_NOTE` 定义,具体包含哪些列要看 `SqlNote` 类中该常量的定义情况。
// 查询的条件是笔记类型不等于系统类型(通过 `(type<>?` 判断,具体值取自 `Notes.TYPE_SYSTEM`)并且父节点 ID 不等于回收站文件夹的 ID通过 `parent_id<>?` 判断,具体值取自 `Notes.ID_TRASH_FOLER`),以筛选出符合要求的笔记记录。
// 最后的排序方式按照 `NoteColumns.TYPE + " DESC"`,也就是按照 `NoteColumns.TYPE` 定义的类型字段进行降序排列,具体排序的逻辑和作用取决于该字段的含义。
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");
// 如果查询成功,即游标 `c` 不为 `null`,则开始遍历查询结果集,对每一条符合条件的笔记记录进行处理。
if (c!= null) {
while (c.moveToNext()) {
// 从当前游标所指向的记录中获取对应的远程节点唯一标识符Gid通过 `SqlNote.GTASK_ID_COLUMN` 指定的列来获取,该列应该存储了节点在远程端对应的唯一标识信息。
String gid = c.getString(SqlNote.GTASK_ID_COLUMN);
// 根据获取到的远程节点 Gid尝试从 `mGTaskHashMap` 映射表中获取对应的节点对象,如果能获取到,表示该远程节点在本地有对应的记录关联。
Node node = mGTaskHashMap.get(gid);
if (node!= null) {
// 如果找到了对应的节点对象,先从 `mGTaskHashMap` 映射表中移除该节点,可能是为了避免重复处理或者更新相关状态等操作(具体要看后续逻辑对该映射表的使用情况)。
mGTaskHashMap.remove(gid);
// 创建一个 `ContentValues` 对象,用于存放要更新到数据库中的数据值,这里主要是要更新同步 ID`NoteColumns.SYNC_ID`)相关的值。
ContentValues values = new ContentValues();
// 将当前节点对象的最后修改时间(通过 `node.getLastModified()` 获取)设置到 `ContentValues` 对象中,对应的键为 `NoteColumns.SYNC_ID`,意味着要将这个最后修改时间更新到本地数据库中对应笔记记录的同步 ID 字段中,以保持本地数据与远程数据在同步方面的一致性。
values.put(NoteColumns.SYNC_ID, node.getLastModified());
// 通过 `ContentResolver` 的 `update` 方法,对本地数据库中对应的笔记记录进行更新操作,更新的记录通过 `ContentUris.withAppendedId` 方法构建的 URI基于 `Notes.CONTENT_NOTE_URI` 并附上当前记录的 `ID`,即 `c.getLong(SqlNote.ID_COLUMN)`)来指定具体要更新的哪条记录,更新的数据就是前面准备好的 `values` 对象中的内容,后面两个 `null` 参数可能分别表示更新的条件(这里没有额外指定条件,即更新所有匹配的记录)和更新参数的选择(默认情况)。
mContentResolver.update(ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI,
c.getLong(SqlNote.ID_COLUMN)), values, null, null);
} else {
// 如果根据 Gid 在 `mGTaskHashMap` 中没有找到对应的节点对象,说明存在本地的笔记记录在同步后没有对应的远程节点关联了,这可能是出现了数据不一致等问题,记录错误日志。
Log.e(TAG, "something is missed");
// 抛出异常,表明出现了同步后本地部分项目没有对应的 Gid 的异常情况,异常类型为 `ActionFailureException`,并附带相应的错误提示信息,方便上层调用者捕获并处理该异常情况。
throw new ActionFailureException(
"some local items don't have gid after sync");
}
}
} else {
// 如果查询游标 `c` 为 `null`,说明查询本地笔记用于刷新同步 ID 的操作失败了,记录相应的警告日志,提示查询本地笔记失败的情况,但不会抛出异常中断整个流程(可能是希望后续可以再次尝试或者只是记录问题供排查)。
Log.w(TAG, "failed to query local note to refresh sync id");
}
} finally {
// 无论前面的查询操作是否成功,都要确保游标对象 `c` 被正确关闭,以释放相关的数据库资源,避免资源泄漏等问题。如果游标不为 `null`,则执行关闭操作,并将游标对象重新赋值为 `null`。
if (c!= null) {
c.close();
c = null;
}
}
}
public String getSyncAccount() {
// 该方法的功能是获取同步账户的名称信息,通过调用 `GTaskClient` 单例实例(通过 `getInstance` 方法获取单例对象)的 `getSyncAccount` 方法获取账户对象,然后再获取其名称属性(通过 `.name`)返回,具体 `GTaskClient` 类中相关方法和属性的实现取决于其具体的业务逻辑和设计,这里返回的就是当前用于同步操作的账户的名称字符串。
return GTaskClient.getInstance().getSyncAccount().name;
}
public void cancelSync() {
// 此方法用于取消同步操作,通过将 `mCancelled` 标志设置为 `true`,在其他相关的同步逻辑方法(比如前面的 `syncContent`、`syncFolder` 等涉及同步操作的方法)中会通过检查这个标志来判断是否需要提前终止同步流程,从而实现取消同步的功能。
mCancelled = true;
}
}