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.
git-test/src/main/java/net/micode/notes/gtask/data/SqlData.java

338 lines
12 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.data;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import net.micode.notes.data.NotesDatabaseHelper.TABLE;
import net.micode.notes.gtask.exception.ActionFailureException;
import org.json.JSONException;
import org.json.JSONObject;
/**
* SqlData类是小米笔记应用中GTask同步模块的核心数据处理类负责本地SQL数据的CRUD操作。CRUD 是数据库操作的四种基本功能缩写:
- CCreate创建新笔记数据
- RRead读取现有笔记数据
- UUpdate更新笔记数据
- DDelete删除笔记数据
* <p>
* 在Notes与Google Tasks的同步过程中该类扮演着桥梁角色
* 1. 接收并解析来自GTask的JSON格式数据
* 2. 将数据转换为本地数据库可存储的格式
* 3. 通过ContentResolver与本地ContentProvider交互执行数据的插入、更新和删除操作
* 4. 支持数据差异跟踪,仅提交修改的字段以优化性能
* 5. 提供版本验证机制,避免同步冲突
* </p>
* <p>
* 该类是同步流程中的关键组件确保本地笔记数据与Google Tasks之间的数据一致性和完整性。
* </p>
*/
public class SqlData {
private static final String TAG = SqlData.class.getSimpleName();
/**
* 无效ID常量用于标记未初始化或无效的数据ID
* 在同步过程中用于区分新创建的数据和已存在的数据
*/
private static final int INVALID_ID = -99999;
/**
* 数据库查询投影列,定义了从数据库中获取的数据字段
* 包含数据ID、MIME类型、内容以及扩展字段DATA1和DATA3
*/
public static final String[] PROJECTION_DATA = new String[] {
DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1,
DataColumns.DATA3
};
/**
* 数据ID列在投影中的索引位置
*/
public static final int DATA_ID_COLUMN = 0;
/**
* 数据MIME类型列在投影中的索引位置
* 用于区分不同类型的数据(如文本笔记、通话记录等)
*/
public static final int DATA_MIME_TYPE_COLUMN = 1;
/**
* 数据内容列在投影中的索引位置
* 存储笔记的主要内容
*/
public static final int DATA_CONTENT_COLUMN = 2;
/**
* 数据内容DATA1字段在投影中的索引位置
* 用于存储整数类型的扩展数据(如笔记模式、通话日期等)
*/
public static final int DATA_CONTENT_DATA_1_COLUMN = 3;
/**
* 数据内容DATA3字段在投影中的索引位置
* 用于存储字符串类型的扩展数据(如电话号码等)
*/
public static final int DATA_CONTENT_DATA_3_COLUMN = 4;
/**
* 用于访问ContentProvider的ContentResolver实例
* 是与本地数据库交互的核心接口
*/
private ContentResolver mContentResolver;
/**
* 标记是否为新建数据
* true表示该数据需要插入数据库false表示需要更新数据库
*/
private boolean mIsCreate;
/**
* 数据ID
* 唯一标识数据库中的数据记录
*/
private long mDataId;
/**
* 数据MIME类型
* 定义数据的类型,如文本笔记(DataConstants.NOTE)或通话记录
*/
private String mDataMimeType;
/**
* 数据内容
* 存储笔记的主要文本内容
*/
private String mDataContent;
/**
* 数据内容的DATA1字段
* 根据MIME类型不同存储不同的整数数据
* - 对于文本笔记:存储笔记模式(普通模式或 checklist 模式)
* - 对于通话记录:存储通话日期
*/
private long mDataContentData1;
/**
* 数据内容的DATA3字段
* 根据MIME类型不同存储不同的字符串数据
* - 对于通话记录:存储电话号码
*/
private String mDataContentData3;
/**
* 用于跟踪数据变化的ContentValues对象
* 只包含已修改的字段,用于优化数据库更新操作
*/
private ContentValues mDiffDataValues;
/**
* 构造函数用于创建新的SqlData实例用于插入新数据
* <p>
* 初始化所有字段为默认值并将mIsCreate标记设置为true
* </p>
*
* @param context 上下文对象用于获取ContentResolver实例
*/
public SqlData(Context context) {
mContentResolver = context.getContentResolver();
mIsCreate = true;
mDataId = INVALID_ID;
mDataMimeType = DataConstants.NOTE;
mDataContent = "";
mDataContentData1 = 0;
mDataContentData3 = "";
mDiffDataValues = new ContentValues();
}
/**
* 构造函数用于从数据库Cursor加载SqlData实例用于更新现有数据
* <p>
* 从Cursor中读取数据并初始化所有字段将mIsCreate标记设置为false
* </p>
*
* @param context 上下文对象用于获取ContentResolver实例
* @param c 数据库Cursor对象包含要加载的数据记录
*/
public SqlData(Context context, Cursor c) {
mContentResolver = context.getContentResolver();
mIsCreate = false;
loadFromCursor(c);
mDiffDataValues = new ContentValues();
}
/**
* 从数据库Cursor中加载数据到当前SqlData实例
* <p>
* 根据PROJECTION_DATA中定义的列顺序从Cursor中读取对应字段的值
* </p>
*
* @param c 包含数据的Cursor对象必须指向有效的数据行
*/
private void loadFromCursor(Cursor c) {
mDataId = c.getLong(DATA_ID_COLUMN);
mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN);
mDataContent = c.getString(DATA_CONTENT_COLUMN);
mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN);
mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN);
}
/**
* 从JSON对象设置数据内容并跟踪数据变化
* <p>
* 该方法是同步过程中的关键方法用于将从GTask获取的JSON数据转换为本地数据格式
* 同时会跟踪数据变化仅将修改的字段记录到mDiffDataValues中
* </p>
*
* @param js 包含GTask数据的JSON对象
* @throws JSONException 如果JSON解析失败
*/
public void setContent(JSONObject js) throws JSONException {
long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID;
if (mIsCreate || mDataId != dataId) {
mDiffDataValues.put(DataColumns.ID, dataId);
}
mDataId = dataId;
String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE)
: DataConstants.NOTE;
if (mIsCreate || !mDataMimeType.equals(dataMimeType)) {
mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType);
}
mDataMimeType = dataMimeType;
String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : "";
if (mIsCreate || !mDataContent.equals(dataContent)) {
mDiffDataValues.put(DataColumns.CONTENT, dataContent);
}
mDataContent = dataContent;
long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0;
if (mIsCreate || mDataContentData1 != dataContentData1) {
mDiffDataValues.put(DataColumns.DATA1, dataContentData1);
}
mDataContentData1 = dataContentData1;
String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : "";
if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {
mDiffDataValues.put(DataColumns.DATA3, dataContentData3);
}
mDataContentData3 = dataContentData3;
}
/**
* 获取当前数据的JSON表示
* <p>
* 将本地数据转换为JSON格式用于与GTask进行数据交换
* 注意只有已提交到数据库的数据才能获取JSON表示
* </p>
*
* @return 包含当前数据的JSON对象如果数据未提交到数据库则返回null
* @throws JSONException 如果JSON构建失败
*/
public JSONObject getContent() throws JSONException {
if (mIsCreate) {
Log.e(TAG, "it seems that we haven't created this in database yet");
return null;
}
JSONObject js = new JSONObject();
js.put(DataColumns.ID, mDataId);
js.put(DataColumns.MIME_TYPE, mDataMimeType);
js.put(DataColumns.CONTENT, mDataContent);
js.put(DataColumns.DATA1, mDataContentData1);
js.put(DataColumns.DATA3, mDataContentData3);
return js;
}
/**
* 将数据提交到本地数据库
* <p>
* 这是SqlData类的核心方法根据mIsCreate标记执行插入或更新操作
* 1. 如果是新数据(mIsCreate=true),执行插入操作
* 2. 如果是现有数据(mIsCreate=false),仅当有数据变化时执行更新操作
* 3. 支持版本验证机制,避免在同步过程中出现数据冲突
* </p>
*
* @param noteId 所属的Note ID用于关联到具体的笔记
* @param validateVersion 是否验证版本true表示需要进行版本检查
* @param version 版本号,用于版本验证
* @throws ActionFailureException 如果数据提交失败
*/
public void commit(long noteId, boolean validateVersion, long version) {
if (mIsCreate) {
if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) {
mDiffDataValues.remove(DataColumns.ID);
}
mDiffDataValues.put(DataColumns.NOTE_ID, noteId);
Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);
try {
mDataId = Long.valueOf(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
Log.e(TAG, "Get note id error :" + e.toString());
throw new ActionFailureException("create note failed");
}
} else {
if (mDiffDataValues.size() > 0) {
int result = 0;
if (!validateVersion) {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues, null, null);
} else {
result = mContentResolver.update(ContentUris.withAppendedId(
Notes.CONTENT_DATA_URI, mDataId), mDiffDataValues,
" ? in (SELECT " + NoteColumns.ID + " FROM " + TABLE.NOTE
+ " WHERE " + NoteColumns.VERSION + "=?)", new String[] {
String.valueOf(noteId), String.valueOf(version)
});
}
if (result == 0) {
Log.w(TAG, "there is no update. maybe user updates note when syncing");
}
}
}
mDiffDataValues.clear();
mIsCreate = false;
}
/**
* 获取数据ID
* <p>
* 返回当前数据的唯一标识符,用于在数据库中定位该记录
* </p>
*
* @return 数据ID如果数据未提交到数据库则返回INVALID_ID
*/
public long getId() {
return mDataId;
}
}