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.
MiNote/gtask/data/SqlNote.java

354 lines
16 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.data;
import android.appwidget.AppWidgetManager;
import android.content.ContentResolver;
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.NoteColumns;
import net.micode.notes.gtask.exception.ActionFailureException;
import net.micode.notes.tool.GTaskStringUtils;
import net.micode.notes.tool.ResourceParser;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
/**
* 便签数据库操作类
* 负责便签Note的数据库增删改查及 JSON 数据转换
*/
public class SqlNote {
private static final String TAG = SqlNote.class.getSimpleName(); // 日志标签
private static final int INVALID_ID = -99999; // 无效 ID 标识
// 便签查询投影(指定查询的数据库字段)
public static final String[] PROJECTION_NOTE = new String[] {
NoteColumns.ID, // 便签 ID
NoteColumns.ALERTED_DATE, // 提醒时间
NoteColumns.BG_COLOR_ID, // 背景颜色 ID
NoteColumns.CREATED_DATE, // 创建时间
NoteColumns.HAS_ATTACHMENT, // 是否有附件0/1
NoteColumns.MODIFIED_DATE, // 修改时间
NoteColumns.NOTES_COUNT, // 子便签数量(用于文件夹)
NoteColumns.PARENT_ID, // 父级 ID用于层级结构
NoteColumns.SNIPPET, // 便签摘要
NoteColumns.TYPE, // 类型(普通便签、文件夹、系统文件夹等)
NoteColumns.WIDGET_ID, // 桌面小部件 ID
NoteColumns.WIDGET_TYPE, // 小部件类型
NoteColumns.SYNC_ID, // 同步 ID用于云同步
NoteColumns.LOCAL_MODIFIED, // 本地修改标记(用于同步冲突检测)
NoteColumns.ORIGIN_PARENT_ID, // 原始父级 ID可能用于同步历史记录
NoteColumns.GTASK_ID, // Google Tasks ID任务同步
NoteColumns.VERSION // 版本号(用于乐观锁)
};
// 投影字段索引(方便通过索引访问字段值)
public static final int ID_COLUMN = 0;
public static final int ALERTED_DATE_COLUMN = 1;
public static final int BG_COLOR_ID_COLUMN = 2;
public static final int CREATED_DATE_COLUMN = 3;
public static final int HAS_ATTACHMENT_COLUMN = 4;
public static final int MODIFIED_DATE_COLUMN = 5;
public static final int NOTES_COUNT_COLUMN = 6;
public static final int PARENT_ID_COLUMN = 7;
public static final int SNIPPET_COLUMN = 8;
public static final int TYPE_COLUMN = 9;
public static final int WIDGET_ID_COLUMN = 10;
public static final int WIDGET_TYPE_COLUMN = 11;
public static final int SYNC_ID_COLUMN = 12;
public static final int LOCAL_MODIFIED_COLUMN = 13;
public static final int ORIGIN_PARENT_ID_COLUMN = 14;
public static final int GTASK_ID_COLUMN = 15;
public static final int VERSION_COLUMN = 16;
private Context mContext; // 上下文(用于获取 ContentResolver
private ContentResolver mContentResolver; // 内容解析器(操作数据库)
private boolean mIsCreate; // 是否为新建便签(未插入数据库)
private long mId; // 便签 ID
private long mAlertDate; // 提醒时间
private int mBgColorId; // 背景颜色 ID默认取系统默认值
private long mCreatedDate; // 创建时间(默认当前时间)
private int mHasAttachment; // 是否有附件(默认无)
private long mModifiedDate; // 修改时间(默认当前时间)
private long mParentId; // 父级 ID默认根目录
private String mSnippet; // 便签摘要(默认空字符串)
private int mType; // 便签类型(默认普通便签)
private int mWidgetId; // 小部件 ID默认无效 ID
private int mWidgetType; // 小部件类型(默认无效类型)
private long mOriginParent; // 原始父级 ID默认 0
private long mVersion; // 版本号(用于数据版本控制)
private ContentValues mDiffNoteValues; // 待更新的字段值(增量更新)
private ArrayList<SqlData> mDataList; // 便签内容列表(普通便签包含文本、图片等子数据)
/**
* 构造函数(新建便签)
* 初始化默认值,标记为新建状态
*/
public SqlNote(Context context) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = true; // 标记为新建
mId = INVALID_ID; // 初始 ID 无效
mAlertDate = 0; // 无提醒时间
// 获取默认背景颜色 ID来自 ResourceParser 工具类)
mBgColorId = ResourceParser.getDefaultBgId(context);
mCreatedDate = System.currentTimeMillis(); // 创建时间为当前时间
mHasAttachment = 0; // 初始无附件
mModifiedDate = System.currentTimeMillis(); // 修改时间为当前时间
mParentId = 0; // 父级默认为根目录
mSnippet = ""; // 摘要为空
mType = Notes.TYPE_NOTE; // 类型为普通便签
// 小部件 ID 设为无效值AppWidgetManager 定义)
mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
mWidgetType = Notes.TYPE_WIDGET_INVALIDE; // 无效小部件类型
mOriginParent = 0; // 原始父级默认 0
mVersion = 0; // 版本号初始为 0
mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合
mDataList = new ArrayList<SqlData>(); // 初始化内容列表
}
/**
* 构造函数(从 Cursor 加载已有便签)
* @param c 数据库查询结果 Cursor
*/
public SqlNote(Context context, Cursor c) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为已存在
loadFromCursor(c); // 从 Cursor 加载数据
mDataList = new ArrayList<SqlData>(); // 初始化内容列表
// 如果是普通便签,加载其子内容(文本、图片等)
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合
}
/**
* 构造函数(通过 ID 加载便签)
* @param id 便签 ID
*/
public SqlNote(Context context, long id) {
mContext = context;
mContentResolver = context.getContentResolver();
mIsCreate = false; // 标记为已存在
loadFromCursor(id); // 通过 ID 从数据库加载数据
mDataList = new ArrayList<SqlData>(); // 初始化内容列表
// 如果是普通便签,加载其子内容
if (mType == Notes.TYPE_NOTE)
loadDataContent();
mDiffNoteValues = new ContentValues(); // 初始化待更新字段集合
}
/**
* 通过 ID 从数据库加载便签数据
* @param id 便签 ID
*/
private void loadFromCursor(long id) {
Cursor c = null;
try {
// 查询便签表,条件为 ID 匹配
c = mContentResolver.query(Notes.CONTENT_NOTE_URI, PROJECTION_NOTE, "(_id=?)",
new String[] { String.valueOf(id) }, null);
if (c != null) {
c.moveToNext(); // 移动到查询结果第一条(假设唯一)
loadFromCursor(c); // 从 Cursor 解析数据
} else {
Log.w(TAG, "loadFromCursor: cursor = null"); // 日志Cursor 为空
}
} finally {
if (c != null)
c.close(); // 关闭 Cursor 释放资源
}
}
/**
* 从 Cursor 解析便签数据到对象属性
* @param c 数据库查询结果 Cursor
*/
private void loadFromCursor(Cursor c) {
// 从 Cursor 中按投影索引获取字段值
mId = c.getLong(ID_COLUMN);
mAlertDate = c.getLong(ALERTED_DATE_COLUMN);
mBgColorId = c.getInt(BG_COLOR_ID_COLUMN);
mCreatedDate = c.getLong(CREATED_DATE_COLUMN);
mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN);
mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN);
mParentId = c.getLong(PARENT_ID_COLUMN);
mSnippet = c.getString(SNIPPET_COLUMN);
mType = c.getInt(TYPE_COLUMN);
mWidgetId = c.getInt(WIDGET_ID_COLUMN);
mWidgetType = c.getInt(WIDGET_TYPE_COLUMN);
mVersion = c.getLong(VERSION_COLUMN); // 加载版本号用于乐观锁
}
/**
* 加载普通便签的子内容(如文本段落、图片等)
* 数据存储在独立的表中,通过 note_id 关联
*/
private void loadDataContent() {
Cursor c = null;
mDataList.clear(); // 清空现有内容列表
try {
// 查询便签内容表,条件为当前便签 ID
c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA,
"(note_id=?)", new String[] { String.valueOf(mId) }, null);
if (c != null) {
if (c.getCount() == 0) {
Log.w(TAG, "it seems that the note has not data"); // 日志:便签无内容
return;
}
// 遍历 Cursor创建 SqlData 对象并添加到列表
while (c.moveToNext()) {
SqlData data = new SqlData(mContext, c);
mDataList.add(data);
}
} else {
Log.w(TAG, "loadDataContent: cursor = null"); // 日志Cursor 为空
}
} finally {
if (c != null)
c.close(); // 关闭 Cursor 释放资源
}
}
/**
* 从 JSON 对象设置便签内容(用于同步数据解析)
* @param js 包含便签数据的 JSON 对象
* @return 是否解析成功
*/
public boolean setContent(JSONObject js) {
try {
// 获取便签头部数据note 节点)
JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);
int type = note.getInt(NoteColumns.TYPE);
// 系统文件夹不可修改
if (type == Notes.TYPE_SYSTEM) {
Log.w(TAG, "cannot set system folder");
return false;
} else if (type == Notes.TYPE_FOLDER) { // 文件夹类型
// 文件夹仅支持更新摘要和类型
String snippet = note.optString(NoteColumns.SNIPPET, ""); // 安全获取摘要
if (mIsCreate || !mSnippet.equals(snippet)) {
// 记录待更新的摘要字段
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet; // 更新本地摘要
// 更新类型(如果有变化)
type = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE);
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type; // 更新本地类型
} else if (type == Notes.TYPE_NOTE) { // 普通便签类型
// 获取内容数组data 节点)
JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);
// 解析便签基础属性
long id = note.optLong(NoteColumns.ID, INVALID_ID); // 安全获取 ID
if (mIsCreate || mId != id) {
mDiffNoteValues.put(NoteColumns.ID, id); // 新建时设置 ID
}
mId = id; // 更新本地 ID
// 解析提醒时间
long alertDate = note.optLong(NoteColumns.ALERTED_DATE, 0);
if (mIsCreate || mAlertDate != alertDate) {
mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate);
}
mAlertDate = alertDate;
// 解析背景颜色 ID默认系统默认值
int bgColorId = note.optInt(NoteColumns.BG_COLOR_ID,
ResourceParser.getDefaultBgId(mContext));
if (mIsCreate || mBgColorId != bgColorId) {
mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId);
}
mBgColorId = bgColorId;
// 解析创建时间(默认当前时间)
long createDate = note.optLong(NoteColumns.CREATED_DATE,
System.currentTimeMillis());
if (mIsCreate || mCreatedDate != createDate) {
mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate);
}
mCreatedDate = createDate;
// 解析附件标记(默认无附件)
int hasAttachment = note.optInt(NoteColumns.HAS_ATTACHMENT, 0);
if (mIsCreate || mHasAttachment != hasAttachment) {
mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment);
}
mHasAttachment = hasAttachment;
// 解析修改时间(默认当前时间)
long modifiedDate = note.optLong(NoteColumns.MODIFIED_DATE,
System.currentTimeMillis());
if (mIsCreate || mModifiedDate != modifiedDate) {
mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate);
}
mModifiedDate = modifiedDate;
// 解析父级 ID默认根目录
long parentId = note.optLong(NoteColumns.PARENT_ID, 0);
if (mIsCreate || mParentId != parentId) {
mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId);
}
mParentId = parentId;
// 解析摘要(默认空字符串)
String snippet = note.optString(NoteColumns.SNIPPET, "");
if (mIsCreate || !mSnippet.equals(snippet)) {
mDiffNoteValues.put(NoteColumns.SNIPPET, snippet);
}
mSnippet = snippet;
// 解析类型(默认普通便签)
type = note.optInt(NoteColumns.TYPE, Notes.TYPE_NOTE);
if (mIsCreate || mType != type) {
mDiffNoteValues.put(NoteColumns.TYPE, type);
}
mType = type;
// 解析小部件 ID默认无效 ID
int widgetId = note.optInt(NoteColumns.WIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
if (mIsCreate || mWidgetId != widgetId) {
mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId);
}
mWidgetId = widgetId;
// 解析小部件类型(默认无效类型)
int widgetType = note.optInt(NoteColumns.WIDGET_TYPE,
Notes.TYPE_WIDGET_INVALIDE);
if (mIsCreate || mWidgetType != widgetType) {
mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType);
}
mWidgetType = widgetType;
// 解析原始父级 ID默认 0
long originParent = note.optLong(NoteColumns.ORIGIN_PARENT_ID,