From 0e013fe7c639f4044eb647cedf81cad469c1a210 Mon Sep 17 00:00:00 2001 From: LHY <3170597338@qq.com> Date: Tue, 5 Nov 2024 10:16:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BF=BB=E8=AF=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ActionFailureException.java | 39 ++ Contact.java | 80 +++++ GTaskASyncTask.java | 149 ++++++++ MetaData.java | 94 +++++ NetworkFailureException.java | 35 ++ Node.java | 102 ++++++ Notes.java | 426 ++++++++++++++++++++++ NotesDatabaseHelper.java | 425 ++++++++++++++++++++++ NotesProvider.java | 351 ++++++++++++++++++ SqlData.java | 200 +++++++++++ SqlNote.java | 667 +++++++++++++++++++++++++++++++++++ Task.java | 411 +++++++++++++++++++++ TaskList.java | 463 ++++++++++++++++++++++++ 13 files changed, 3442 insertions(+) create mode 100644 ActionFailureException.java create mode 100644 Contact.java create mode 100644 GTaskASyncTask.java create mode 100644 MetaData.java create mode 100644 NetworkFailureException.java create mode 100644 Node.java create mode 100644 Notes.java create mode 100644 NotesDatabaseHelper.java create mode 100644 NotesProvider.java create mode 100644 SqlData.java create mode 100644 SqlNote.java create mode 100644 Task.java create mode 100644 TaskList.java diff --git a/ActionFailureException.java b/ActionFailureException.java new file mode 100644 index 0000000..e9a9a28 --- /dev/null +++ b/ActionFailureException.java @@ -0,0 +1,39 @@ +/* + * 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. + */ + +// 定义在net.micode.notes.gtask.exception包下的自定义异常类,继承自运行时异常RuntimeException +package net.micode.notes.gtask.exception; + +// 自定义异常类ActionFailureException,用于处理操作失败的情况 +public class ActionFailureException extends RuntimeException { + // 序列化版本号,用于确保序列化与反序列化的兼容性 + private static final long serialVersionUID = 4425249765923293627L; + + // 默认构造方法,调用父类RuntimeException的无参构造方法 + public ActionFailureException() { + super(); + } + + // 带有错误消息的构造方法,调用父类RuntimeException的带有一个字符串参数的构造方法 + public ActionFailureException(String paramString) { + super(paramString); + } + + // 带有错误消息和异常原因的构造方法,调用父类RuntimeException的带有字符串和Throwable参数的构造方法 + public ActionFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } +} diff --git a/Contact.java b/Contact.java new file mode 100644 index 0000000..b23e95e --- /dev/null +++ b/Contact.java @@ -0,0 +1,80 @@ +/* + * 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.data; + +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Data; +import android.telephony.PhoneNumberUtils; +import android.util.Log; + +import java.util.HashMap; + +public class Contact { + private static HashMap sContactCache;// 缓存联系人信息,以电话号码为键,联系人姓名为值 + private static final String TAG = "Contact";// 日志标签 + // 用于查询联系人的SQL选择语句,使用PHONE_NUMBERS_EQUAL函数比较电话号码 + private static final String CALLER_ID_SELECTION = "PHONE_NUMBERS_EQUAL(" + Phone.NUMBER + + ",?) AND " + Data.MIMETYPE + "='" + Phone.CONTENT_ITEM_TYPE + "'" + + " AND " + Data.RAW_CONTACT_ID + " IN " + + "(SELECT raw_contact_id " + + " FROM phone_lookup" + + " WHERE min_match = '+')"; + /** + * 根据电话号码获取联系人姓名。 + * @param context 应用上下文 + * @param phoneNumber 电话号码 + * @return 联系人姓名,如果没有找到则返回null + */ + public static String getContact(Context context, String phoneNumber) { + // 初始化联系人缓存 + if(sContactCache == null) { + sContactCache = new HashMap(); + } +// 如果缓存中已存在该电话号码的联系人信息,直接返回 + if(sContactCache.containsKey(phoneNumber)) { + return sContactCache.get(phoneNumber); + } + // 替换SQL选择语句中的占位符,使用PhoneNumberUtils转换电话号码 + String selection = CALLER_ID_SELECTION.replace("+", + PhoneNumberUtils.toCallerIDMinMatch(phoneNumber)); + Cursor cursor = context.getContentResolver().query( // 查询联系人信息 + Data.CONTENT_URI, + new String [] { Phone.DISPLAY_NAME },// 查询联系人姓名 + selection, + new String[] { phoneNumber },// 查询条件 + null); + // 如果查询结果不为空,处理查询结果 + if (cursor != null && cursor.moveToFirst()) { + try { + String name = cursor.getString(0); // 获取联系人姓名 + sContactCache.put(phoneNumber, name); // 将结果存入缓存 + return name; + } catch (IndexOutOfBoundsException e) { // 捕获并记录异常 + Log.e(TAG, " Cursor get string error " + e.toString()); + return null; + } finally { + cursor.close(); // 关闭游标 + } + } else { + // 如果没有找到匹配的联系人,记录日志并返回null + Log.d(TAG, "No contact matched with number:" + phoneNumber); + return null; + } + } +} \ No newline at end of file diff --git a/GTaskASyncTask.java b/GTaskASyncTask.java new file mode 100644 index 0000000..ebba332 --- /dev/null +++ b/GTaskASyncTask.java @@ -0,0 +1,149 @@ + +/* + * 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.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + +import net.micode.notes.R; +import net.micode.notes.ui.NotesListActivity; +import net.micode.notes.ui.NotesPreferenceActivity; + +// 定义一个异步任务类,继承自AsyncTask,用于执行后台任务 +public class GTaskASyncTask extends AsyncTask { + + // 定义一个常量,用作通知的唯一标识 + private static int GTASK_SYNC_NOTIFICATION_ID = 5234235; + + // 定义一个接口,用于通知任务完成 + public interface OnCompleteListener { + void onComplete(); + } + + private Context mContext; // 上下文对象,用于获取系统服务等 + + private NotificationManager mNotifiManager; // 通知管理器,用于管理通知 + + private GTaskManager mTaskManager; // 任务管理器,用于处理具体的任务 + + private OnCompleteListener mOnCompleteListener; // 完成监听器,用于通知任务完成 + + + // 构造方法,初始化上下文和完成监听器,获取通知管理器和任务管理器的实例 + public GTaskASyncTask(Context context, OnCompleteListener listener) { + mContext = context; + mOnCompleteListener = listener; + mNotifiManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + mTaskManager = GTaskManager.getInstance(); + } + // 取消同步任务的方法 + public void cancelSync() { + mTaskManager.cancelSync(); + } + + // 发布进度信息的方法,将字符串消息转换为字符串数组,然后调用publishProgress + public void publishProgess(String message) { + publishProgress(new String[] { + message + }); + } +// 私有方法,用于显示通知 + private void showNotification(int tickerId, String content) { + // 创建一个通知,指定图标、提示文本和时间戳 + Notification notification = new Notification(R.drawable.notification, mContext + .getString(tickerId), System.currentTimeMillis()); + // 设置通知的默认行为,这里是点亮LED灯 + notification.defaults = Notification.DEFAULT_LIGHTS; + // 设置通知的标志,这里是自动取消 + notification.flags = Notification.FLAG_AUTO_CANCEL; + // 创建一个待定意图,用于点击通知后的跳转 + PendingIntent pendingIntent; + // 根据不同的tickerId,设置不同的跳转活动 + if (tickerId != R.string.ticker_success) { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesPreferenceActivity.class), 0); + + } else { + pendingIntent = PendingIntent.getActivity(mContext, 0, new Intent(mContext, + NotesListActivity.class), 0); + } + // 设置通知的详细信息 + notification.setLatestEventInfo(mContext, mContext.getString(R.string.app_name), content, + pendingIntent); + + // 通过通知管理器发送通知 + mNotifiManager.notify(GTASK_SYNC_NOTIFICATION_ID, notification); + } +// 重写doInBackground方法,该方法在后台线程中执行异步任务 + @Override + protected Integer doInBackground(Void... unused) { + + // 发布进度更新,显示登录进度 + publishProgess(mContext.getString(R.string.sync_progress_login, NotesPreferenceActivity + .getSyncAccountName(mContext))); + + // 执行同步操作,并返回结果 + return mTaskManager.sync(mContext, this); + } +// 重写onProgressUpdate方法,该方法在主线程中执行,用于更新进度 + @Override + protected void onProgressUpdate(String... progress) { + // 显示同步中的通知 + showNotification(R.string.ticker_syncing, progress[0]); + + // 如果上下文是GTaskSyncService的实例,则发送广播 + if (mContext instanceof GTaskSyncService) { + ((GTaskSyncService) mContext).sendBroadcast(progress[0]); + } + } + // 重写onPostExecute方法,该方法在异步任务执行完毕后被调用 + @Override + protected void onPostExecute(Integer result) { + // 根据异步任务的结果执行不同的操作 + if (result == GTaskManager.STATE_SUCCESS) { + // 如果任务成功,显示成功通知,并更新同步时间 + showNotification(R.string.ticker_success, mContext.getString( + R.string.success_sync_account, mTaskManager.getSyncAccount())); + NotesPreferenceActivity.setLastSyncTime(mContext, System.currentTimeMillis()); + } else if (result == GTaskManager.STATE_NETWORK_ERROR) { + // 如果网络错误,显示网络错误通知 + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_network)); + } else if (result == GTaskManager.STATE_INTERNAL_ERROR) { + // 如果内部错误,显示内部错误通知 + showNotification(R.string.ticker_fail, mContext.getString(R.string.error_sync_internal)); + } else if (result == GTaskManager.STATE_SYNC_CANCELLED) { + // 如果同步被取消,显示同步取消通知 + showNotification(R.string.ticker_cancel, mContext + .getString(R.string.error_sync_cancelled)); + } + // 如果存在完成监听器,则在新的线程中调用完成回调 + if (mOnCompleteListener != null) { + new Thread(new Runnable() { + // 实现Runnable接口的run方法 + public void run() { + mOnCompleteListener.onComplete(); + } + }).start(); + } + } +} diff --git a/MetaData.java b/MetaData.java new file mode 100644 index 0000000..4a15ff8 --- /dev/null +++ b/MetaData.java @@ -0,0 +1,94 @@ +/* + * 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.database.Cursor; +import android.util.Log; + +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +// MetaData类继承自Task类 +public class MetaData extends Task { + // 日志标签,用于记录日志 + private final static String TAG = MetaData.class.getSimpleName(); + // 关联的任务ID + private String mRelatedGid = null; + // 设置元数据信息 + public void setMeta(String gid, JSONObject metaInfo) { + try { + // 将关联的任务ID放入metaInfo对象中 + metaInfo.put(GTaskStringUtils.META_HEAD_GTASK_ID, gid); + } catch (JSONException e) { + // 如果发生异常,记录错误日志 + Log.e(TAG, "failed to put related gid"); + } + // 将metaInfo对象转换为字符串并保存 + setNotes(metaInfo.toString()); + // 设置笔记名称 + setName(GTaskStringUtils.META_NOTE_NAME); + } + // 获取关联的任务ID + public String getRelatedGid() { + return mRelatedGid; + } + // 重写isWorthSaving方法,判断是否值得保存 + @Override + public boolean isWorthSaving() { + // 如果笔记内容不为空,则值得保存 + return getNotes() != null; + } + // 重写setContentByRemoteJSON方法,根据远程JSON设置内容 + @Override + public void setContentByRemoteJSON(JSONObject js) { + // 调用父类的方法 + super.setContentByRemoteJSON(js); + // 如果笔记内容不为空,则解析并获取关联的任务ID + if (getNotes() != null) { + try { + JSONObject metaInfo = new JSONObject(getNotes().trim()); + mRelatedGid = metaInfo.getString(GTaskStringUtils.META_HEAD_GTASK_ID); + } catch (JSONException e) { + // 如果发生异常,记录警告日志并设置关联的任务ID为null + Log.w(TAG, "failed to get related gid"); + mRelatedGid = null; + } + } + } + // 重写setContentByLocalJSON方法,此方法不应该被调用 + @Override + public void setContentByLocalJSON(JSONObject js) { + // 抛出非法访问错误 + // this function should not be called + throw new IllegalAccessError("MetaData:setContentByLocalJSON should not be called"); + } + // 重写getLocalJSONFromContent方法,此方法不应该被调用 + @Override + public JSONObject getLocalJSONFromContent() { + // 抛出非法访问错误 + throw new IllegalAccessError("MetaData:getLocalJSONFromContent should not be called"); + } + // 重写getSyncAction方法,此方法不应该被调用 + @Override + public int getSyncAction(Cursor c) { + // 抛出非法访问错误 + throw new IllegalAccessError("MetaData:getSyncAction should not be called"); + } + +} diff --git a/NetworkFailureException.java b/NetworkFailureException.java new file mode 100644 index 0000000..e2ae025 --- /dev/null +++ b/NetworkFailureException.java @@ -0,0 +1,35 @@ +/* + * 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. + */ +// 定义在net.micode.notes.gtask.exception包下的自定义异常类,继承自RuntimeException +package net.micode.notes.gtask.exception; + +public class NetworkFailureException extends Exception { + // serialVersionUID用于序列化,保证版本兼容性 + private static final long serialVersionUID = 2107610287180234136L; + // 无参构造方法,调用父类的无参构造方法 + public NetworkFailureException() { + super(); + } + // 带有一个字符串参数的构造方法,调用父类的带有一个字符串参数的构造方法 + public NetworkFailureException(String paramString) { + super(paramString); + } + // 带有一个字符串和一个Throwable参数的构造方法,调用父类的带有字符串和Throwable参数的构造方法 + public NetworkFailureException(String paramString, Throwable paramThrowable) { + super(paramString, paramThrowable); + } + +} diff --git a/Node.java b/Node.java new file mode 100644 index 0000000..bffd7ba --- /dev/null +++ b/Node.java @@ -0,0 +1,102 @@ +/* + * 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. + */ +// Node类定义了同步操作的基本方法和属性 +package net.micode.notes.gtask.data; + +import android.database.Cursor; + +import org.json.JSONObject; + +public abstract class Node { + // 定义同步操作的常量 + public static final int SYNC_ACTION_NONE = 0; + // 无需同步操作 + public static final int SYNC_ACTION_ADD_REMOTE = 1;// 在远程添加 + + public static final int SYNC_ACTION_ADD_LOCAL = 2;// 在本地添加 + + public static final int SYNC_ACTION_DEL_REMOTE = 3;// 在远程删除 + + public static final int SYNC_ACTION_DEL_LOCAL = 4;// 在本地删除 + + public static final int SYNC_ACTION_UPDATE_REMOTE = 5; // 更新远程 + + public static final int SYNC_ACTION_UPDATE_LOCAL = 6;// 更新本地 + + public static final int SYNC_ACTION_UPDATE_CONFLICT = 7; // 更新冲突 + + public static final int SYNC_ACTION_ERROR = 8;// 同步错误 + // 节点的全局唯一标识符 + private String mGid; + // 节点的名称 + private String mName; + // 节点最后修改时间 + private long mLastModified; + // 节点是否已被删除 + private boolean mDeleted; +// 默认构造函数 + public Node() { + mGid = null; + mName = ""; + mLastModified = 0; + mDeleted = false; + } + // 抽象方法,获取创建操作的JSON对象 + public abstract JSONObject getCreateAction(int actionId); + // 抽象方法,获取更新操作的JSON对象 + public abstract JSONObject getUpdateAction(int actionId); + // 抽象方法,根据远程JSON设置内容 + public abstract void setContentByRemoteJSON(JSONObject js); + // 抽象方法,根据本地JSON设置内容 + public abstract void setContentByLocalJSON(JSONObject js); + // 抽象方法,从内容获取本地JSON对象 + public abstract JSONObject getLocalJSONFromContent(); + // 抽象方法,获取同步操作类型 + public abstract int getSyncAction(Cursor c); + // 设置全局唯一标识符 + public void setGid(String gid) { + this.mGid = gid; + } + // 设置节点名称 + public void setName(String name) { + this.mName = name; + } + // 设置最后修改时间 + public void setLastModified(long lastModified) { + this.mLastModified = lastModified; + } + // 设置节点是否已删除 + public void setDeleted(boolean deleted) { + this.mDeleted = deleted; + } + // 获取全局唯一标识符 + public String getGid() { + return this.mGid; + } + // 获取节点名称 + public String getName() { + return this.mName; + } + // 获取最后修改时间 + public long getLastModified() { + return this.mLastModified; + } + // 获取节点是否已删除 + public boolean getDeleted() { + return this.mDeleted; + } + +} diff --git a/Notes.java b/Notes.java new file mode 100644 index 0000000..f6e6993 --- /dev/null +++ b/Notes.java @@ -0,0 +1,426 @@ +/* + * 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.data; + +import android.net.Uri; +// 笔记类,包含笔记相关的常量和URI +public class Notes { + public static final String AUTHORITY = "micode_notes"; // 内容提供者的授权字符串 + public static final String TAG = "Notes";// 用于日志记录的标签 + // 笔记类型常量 + public static final int TYPE_NOTE = 0; // 普通笔记 + public static final int TYPE_FOLDER = 1;// 文件夹 + public static final int TYPE_SYSTEM = 2;// 系统笔记 + + /** + * Following IDs are system folders' identifiers + * {@link Notes#ID_ROOT_FOLDER } is default folder + * {@link Notes#ID_TEMPARAY_FOLDER } is for notes belonging no folder + * {@link Notes#ID_CALL_RECORD_FOLDER} is to store call records + */ + + /** + * 以下ID是系统文件夹的标识符 + * {@link Notes#ID_ROOT_FOLDER } 是默认文件夹 + * {@link Notes#ID_TEMPARAY_FOLDER } 用于不属于任何文件夹的笔记 + * {@link Notes#ID_CALL_RECORD_FOLDER} 用于存储通话记录 + */ + public static final int ID_ROOT_FOLDER = 0;// 默认文件夹 + public static final int ID_TEMPARAY_FOLDER = -1;// 临时文件夹 + public static final int ID_CALL_RECORD_FOLDER = -2;// 通话记录文件夹 + public static final int ID_TRASH_FOLER = -3;// 垃圾文件夹 + // Intent额外的键,用于传递意图中的额外数据 + public static final String INTENT_EXTRA_ALERT_DATE = "net.micode.notes.alert_date"; // 警报日期 + public static final String INTENT_EXTRA_BACKGROUND_ID = "net.micode.notes.background_color_id"; // 背景颜色ID + public static final String INTENT_EXTRA_WIDGET_ID = "net.micode.notes.widget_id";// 小部件ID + public static final String INTENT_EXTRA_WIDGET_TYPE = "net.micode.notes.widget_type";// 小部件类型 + public static final String INTENT_EXTRA_FOLDER_ID = "net.micode.notes.folder_id"; // 文件夹ID + public static final String INTENT_EXTRA_CALL_DATE = "net.micode.notes.call_date";// 通话日期 + // 小部件类型常量 + public static final int TYPE_WIDGET_INVALIDE = -1; // 无效的小部件类型 + public static final int TYPE_WIDGET_2X = 0;// 2x2大小的小部件 + public static final int TYPE_WIDGET_4X = 1; // 4x4大小的小部件 + // 数据常量内部类 + public static class DataConstants { + public static final String NOTE = TextNote.CONTENT_ITEM_TYPE;// 文本笔记的MIME类型 + public static final String CALL_NOTE = CallNote.CONTENT_ITEM_TYPE;// 通话笔记的MIME类型 + } + + /** + * Uri to query all notes and folders + */ + + /** + * 查询所有笔记和文件夹的URI + */ + public static final Uri CONTENT_NOTE_URI = Uri.parse("content://" + AUTHORITY + "/note"); + + /** + * Uri to query data + */ + /** + * 查询数据的URI + */ + public static final Uri CONTENT_DATA_URI = Uri.parse("content://" + AUTHORITY + "/data"); + // 笔记列接口 + public interface NoteColumns { + // 接口中的常量和方法定义 + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + /** + * 行的唯一ID + *

类型:INTEGER (长整型)

+ */ + public static final String ID = "_id"; + + /** + * The parent's id for note or folder + *

Type: INTEGER (long)

+ */ + /** + * 笔记或文件夹的父ID + *

类型:INTEGER (长整型)

+ */ + public static final String PARENT_ID = "parent_id"; + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + /** + * 笔记或文件夹的创建日期 + *

类型:INTEGER (长整型)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + + + /** + * 最后修改日期 + *

类型:INTEGER (长整型)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + + /** + * Alert date + *

Type: INTEGER (long)

+ */ + /** + * 警报日期 + *

类型:INTEGER (长整型)

+ */ + public static final String ALERTED_DATE = "alert_date"; + + /** + * Folder's name or text content of note + *

Type: TEXT

+ */ + /** + * 文件夹的名称或笔记的文本内容 + *

类型:TEXT

+ */ + public static final String SNIPPET = "snippet"; + + /** + * Note's widget id + *

Type: INTEGER (long)

+ */ + + /** + * 笔记的小部件ID + *

类型:INTEGER (长整型)

+ */ + public static final String WIDGET_ID = "widget_id"; + + /** + * Note's widget type + *

Type: INTEGER (long)

+ */ + /** + * 笔记的小部件类型 + *

类型:INTEGER (长整型)

+ */ + public static final String WIDGET_TYPE = "widget_type"; + + /** + * Note's background color's id + *

Type: INTEGER (long)

+ */ + + /** + * 笔记背景颜色的ID + *

类型:INTEGER (长整型)

+ */ + public static final String BG_COLOR_ID = "bg_color_id"; + + /** + * For text note, it doesn't has attachment, for multi-media + * note, it has at least one attachment + *

Type: INTEGER

+ */ + /** + * 对于文本笔记,它没有附件;对于多媒体笔记,它至少有一个附件 + *

类型:INTEGER

+ */ + public static final String HAS_ATTACHMENT = "has_attachment"; + + /** + * Folder's count of notes + *

Type: INTEGER (long)

+ */ + /** + * 文件夹中的笔记数量 + *

类型:INTEGER (长整型)

+ */ + public static final String NOTES_COUNT = "notes_count"; + + /** + * The file type: folder or note + *

Type: INTEGER

+ */ + /** + * 文件类型:文件夹或笔记 + *

类型:INTEGER

+ */ + public static final String TYPE = "type"; + + /** + * The last sync id + *

Type: INTEGER (long)

+ */ + /** + * 最后同步的ID + *

类型:INTEGER (长整型)

+ */ + public static final String SYNC_ID = "sync_id"; + + /** + * Sign to indicate local modified or not + *

Type: INTEGER

+ */ + /** + * 标志位,指示是否本地修改 + *

类型:INTEGER

+ */ + public static final String LOCAL_MODIFIED = "local_modified"; + + /** + * Original parent id before moving into temporary folder + *

Type : INTEGER

+ */ + /** + * 移动到临时文件夹之前的原始父ID + *

类型:INTEGER

+ */ + public static final String ORIGIN_PARENT_ID = "origin_parent_id"; + + /** + * The gtask id + *

Type : TEXT

+ */ + /** + * GTasks的ID + *

类型:TEXT

+ */ + public static final String GTASK_ID = "gtask_id"; + + /** + * The version code + *

Type : INTEGER (long)

+ */ + /** + * 版本号 + *

类型:INTEGER (长整型)

+ */ + public static final String VERSION = "version"; + } +// 数据列接口 + public interface DataColumns { + /** + * The unique ID for a row + *

Type: INTEGER (long)

+ */ + /** + * 行的唯一ID + *

类型:INTEGER (长整型)

+ */ + public static final String ID = "_id"; + + /** + * The MIME type of the item represented by this row. + *

Type: Text

+ */ + + /** + * 此行代表的项目的MIME类型 + *

类型:TEXT

+ */ + public static final String MIME_TYPE = "mime_type"; + + /** + * The reference id to note that this data belongs to + *

Type: INTEGER (long)

+ */ + /** + * 指向此数据所属笔记的引用ID + *

类型:INTEGER (长整型)

+ */ + public static final String NOTE_ID = "note_id"; + + /** + * Created data for note or folder + *

Type: INTEGER (long)

+ */ + /** + * 笔记或文件夹的创建日期 + *

类型:INTEGER (长整型)

+ */ + public static final String CREATED_DATE = "created_date"; + + /** + * Latest modified date + *

Type: INTEGER (long)

+ */ + + /** + * 最后修改日期 + *

类型:INTEGER (长整型)

+ */ + public static final String MODIFIED_DATE = "modified_date"; + + /** + * Data's content + *

Type: TEXT

+ */ + /** + * 数据的内容 + *

类型:TEXT

+ */ + public static final String CONTENT = "content"; + + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + /** + * 通用数据列,含义由{@link #MIMETYPE}指定,用于整型数据类型 + *

类型:INTEGER

+ */ + public static final String DATA1 = "data1"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * integer data type + *

Type: INTEGER

+ */ + /** + * 通用数据列,具体含义取决于{@link #MIMETYPE},用于整型数据类型 + *

类型:INTEGER

+ */ + public static final String DATA2 = "data2"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + /** + * 通用数据列,具体含义取决于{@link #MIMETYPE},用于文本数据类型 + *

类型:TEXT

+ */ + public static final String DATA3 = "data3"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + /** + * 通用数据列,具体含义取决于{@link #MIMETYPE},用于文本数据类型 + *

类型:TEXT

+ */ + public static final String DATA4 = "data4"; + + /** + * Generic data column, the meaning is {@link #MIMETYPE} specific, used for + * TEXT data type + *

Type: TEXT

+ */ + /** + * 通用数据列,具体含义取决于{@link #MIMETYPE},用于文本数据类型 + *

类型:TEXT

+ */ + public static final String DATA5 = "data5"; + } +// 文本笔记类,实现了DataColumns接口 + public static final class TextNote implements DataColumns { + /** + * Mode to indicate the text in check list mode or not + *

Type: Integer 1:check list mode 0: normal mode

+ */ + /** + * 模式,用于指示文本是否处于勾选列表模式 + *

类型:整型 1:勾选列表模式 0:正常模式

+ */ + public static final String MODE = DATA1; + // 勾选列表模式常量 + public static final int MODE_CHECK_LIST = 1; + // 内容类型,用于指定返回多行数据的MIME类型 + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/text_note"; + // 内容项类型,用于指定返回单行数据的MIME类型 + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/text_note"; + // 内容URI,用于访问文本笔记的数据 + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/text_note"); + } +// 通话笔记类,实现了DataColumns接口 + public static final class CallNote implements DataColumns { + /** + * Call date for this record + *

Type: INTEGER (long)

+ */ + /** + * 此记录的通话日期 + *

类型:INTEGER (长整型)

+ */ + public static final String CALL_DATE = DATA1; + + /** + * Phone number for this record + *

Type: TEXT

+ */ + /** + * 此记录的电话号码 + *

类型:TEXT

+ */ + public static final String PHONE_NUMBER = DATA3; + + public static final String CONTENT_TYPE = "vnd.android.cursor.dir/call_note";// 内容类型,用于指定返回多行数据的MIME类型 + + public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/call_note";// 内容项类型,用于指定返回单行数据的MIME类型 + + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/call_note");// 内容URI,用于访问通话笔记的数据 + } +} diff --git a/NotesDatabaseHelper.java b/NotesDatabaseHelper.java new file mode 100644 index 0000000..7e58f34 --- /dev/null +++ b/NotesDatabaseHelper.java @@ -0,0 +1,425 @@ +/* + * 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.data; + +import android.content.ContentValues; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.util.Log; + +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.DataConstants; +import net.micode.notes.data.Notes.NoteColumns; + +// 笔记数据库助手类,继承自SQLiteOpenHelper +public class NotesDatabaseHelper extends SQLiteOpenHelper { + // 数据库文件名常量 + private static final String DB_NAME = "note.db"; + // 数据库版本常量 + private static final int DB_VERSION = 4; + // 内部接口,定义表名常量 + public interface TABLE { + // 笔记表名 + public static final String NOTE = "note"; + // 数据表名 + public static final String DATA = "data"; + } + // 日志标签常量 + private static final String TAG = "NotesDatabaseHelper"; + // 单例模式,用于存储数据库助手实例 + private static NotesDatabaseHelper mInstance; +// 创建笔记表的SQL语句常量 + private static final String CREATE_NOTE_TABLE_SQL = + "CREATE TABLE " + TABLE.NOTE + "(" + + NoteColumns.ID + " INTEGER PRIMARY KEY," +// 主键,笔记ID + NoteColumns.PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 父笔记ID,非空,默认值为0 + NoteColumns.ALERTED_DATE + " INTEGER NOT NULL DEFAULT 0," +// 提醒日期,非空,默认值为0 + NoteColumns.BG_COLOR_ID + " INTEGER NOT NULL DEFAULT 0," +// 背景颜色ID,非空,默认值为0 + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 创建日期,非空,默认为当前时间戳 + NoteColumns.HAS_ATTACHMENT + " INTEGER NOT NULL DEFAULT 0," + // 是否有附件,非空,默认值为0 + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 修改日期,非空,默认为当前时间戳 + NoteColumns.NOTES_COUNT + " INTEGER NOT NULL DEFAULT 0," +// 笔记计数,非空,默认值为0 + NoteColumns.SNIPPET + " TEXT NOT NULL DEFAULT ''," +// 笔记摘要,非空,默认为空字符串 + NoteColumns.TYPE + " INTEGER NOT NULL DEFAULT 0," + // 笔记类型,非空,默认值为0 + NoteColumns.WIDGET_ID + " INTEGER NOT NULL DEFAULT 0," +// 小部件ID,非空,默认值为0 + NoteColumns.WIDGET_TYPE + " INTEGER NOT NULL DEFAULT -1," + // 小部件类型,非空,默认值为-1 + NoteColumns.SYNC_ID + " INTEGER NOT NULL DEFAULT 0," + // 同步ID,非空,默认值为0 + NoteColumns.LOCAL_MODIFIED + " INTEGER NOT NULL DEFAULT 0," + // 本地修改标记,非空,默认值为0 + NoteColumns.ORIGIN_PARENT_ID + " INTEGER NOT NULL DEFAULT 0," +// 原始父笔记ID,非空,默认值为0 + NoteColumns.GTASK_ID + " TEXT NOT NULL DEFAULT ''," + // GTask ID,非空,默认为空字符串 + NoteColumns.VERSION + " INTEGER NOT NULL DEFAULT 0" + // 版本号,整型,非空,默认值0 + ")"; + +// 定义创建数据表的SQL语句常量 + private static final String CREATE_DATA_TABLE_SQL = + "CREATE TABLE " + TABLE.DATA + "(" + + DataColumns.ID + " INTEGER PRIMARY KEY," + // 数据ID,整型,主键 + DataColumns.MIME_TYPE + " TEXT NOT NULL," +// MIME类型,文本,非空 + DataColumns.NOTE_ID + " INTEGER NOT NULL DEFAULT 0," +// 关联的笔记ID,整型,非空,默认值0 + NoteColumns.CREATED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 创建日期,整型,非空,默认值为当前时间戳(单位:毫秒) + NoteColumns.MODIFIED_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now') * 1000)," +// 修改日期,整型,非空,默认值为当前时间戳(单位:毫秒) + DataColumns.CONTENT + " TEXT NOT NULL DEFAULT ''," +// 内容,文本,非空,默认值空字符串 + DataColumns.DATA1 + " INTEGER," +// 数据1,整型 + DataColumns.DATA2 + " INTEGER," + // 数据2,整型 + DataColumns.DATA3 + " TEXT NOT NULL DEFAULT ''," +// 数据3,文本,非空,默认值空字符串 + DataColumns.DATA4 + " TEXT NOT NULL DEFAULT ''," +// 数据4,文本,非空,默认值空字符串 + DataColumns.DATA5 + " TEXT NOT NULL DEFAULT ''" +// 数据5,文本,非空,默认值空字符串 + ")"; +// 定义创建数据表中笔记ID索引的SQL语句常量 + private static final String CREATE_DATA_NOTE_ID_INDEX_SQL = + "CREATE INDEX IF NOT EXISTS note_id_index ON " + + TABLE.DATA + "(" + DataColumns.NOTE_ID + ");";// 如果不存在,则创建一个名为note_id_index的索引,基于关联的笔记ID + + /** + * Increase folder's note count when move note to the folder + */ +/** + * 当笔记移动到文件夹时,增加文件夹的笔记计数 + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_update "+ // 创建一个名为increase_folder_count_on_update的触发器 + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + // 在更新笔记表中的父ID字段后触发 + " BEGIN " +// 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +// 将笔记计数加1 + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +// 条件是当前更新的笔记的父ID + " END";// 触发器执行结束 + + /** + * Decrease folder's note count when move note from folder + */ +/** + * 当从文件夹中移动笔记时,减少文件夹的笔记计数 + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_update " +// 创建一个名为decrease_folder_count_on_update的触发器 + " AFTER UPDATE OF " + NoteColumns.PARENT_ID + " ON " + TABLE.NOTE + // 在更新笔记表中的父ID字段后触发 + " BEGIN " +// 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +// 将笔记计数减1 + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 条件是更新前的笔记的父ID + " AND " + NoteColumns.NOTES_COUNT + ">0" + ";" +// 并且笔记计数大于0 + " END"; // 触发器执行结束 + + /** + * Increase folder's note count when insert new note to the folder + */ +/** + * 当向文件夹中插入新笔记时,增加文件夹的笔记计数 + */ + private static final String NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER = + "CREATE TRIGGER increase_folder_count_on_insert " + // 创建一个名为increase_folder_count_on_insert的触发器 + " AFTER INSERT ON " + TABLE.NOTE + // 在向笔记表中插入新记录后触发 + " BEGIN " + // 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + " + 1" +// 将笔记计数加1 + " WHERE " + NoteColumns.ID + "=new." + NoteColumns.PARENT_ID + ";" +// 条件是插入的新笔记的父ID + " END";// 触发器执行结束 + + /** + * Decrease folder's note count when delete note from the folder + */ +/** + * 当从文件夹中删除笔记时,减少文件夹的笔记计数 + */ + private static final String NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER = + "CREATE TRIGGER decrease_folder_count_on_delete " + // 创建一个名为decrease_folder_count_on_delete的触发器 + " AFTER DELETE ON " + TABLE.NOTE +// 在从笔记表中删除记录后触发 + " BEGIN " + // 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.NOTES_COUNT + "=" + NoteColumns.NOTES_COUNT + "-1" +// 将笔记计数减1 + " WHERE " + NoteColumns.ID + "=old." + NoteColumns.PARENT_ID + // 条件是删除的笔记的父I + " AND " + NoteColumns.NOTES_COUNT + ">0;" + // 并且笔记计数大于0 + " END";// 触发器执行结束 + + /** + * Update note's content when insert data with type {@link DataConstants#NOTE} + */ +/** + * 当插入类型为{@link DataConstants#NOTE}的数据时,更新笔记的内容 + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER = + "CREATE TRIGGER update_note_content_on_insert " + // 创建一个名为update_note_content_on_insert的触发器 + " AFTER INSERT ON " + TABLE.DATA +// 在数据表插入新记录后触发 + " WHEN new." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当新记录的类型为笔记时 + " BEGIN" + // 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT + // 将笔记的摘要设置为新的内容 + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" +// 条件是笔记ID与新记录的笔记ID相匹配 + " END";// 触发器执行结束 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has changed + */ + +/** + * 当类型为{@link DataConstants#NOTE}的数据发生变化时,更新笔记的内容 + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER = + "CREATE TRIGGER update_note_content_on_update " +// 创建一个名为update_note_content_on_update的触发器 + " AFTER UPDATE ON " + TABLE.DATA + // 在数据表更新记录后触发 + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当被更新记录的类型为笔记时 + " BEGIN" +// 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.SNIPPET + "=new." + DataColumns.CONTENT +// 将笔记的摘要设置为新的内容 + " WHERE " + NoteColumns.ID + "=new." + DataColumns.NOTE_ID + ";" + // 条件是笔记ID与新记录的笔记ID相匹配 + " END"; // 触发器执行结束 + + /** + * Update note's content when data with {@link DataConstants#NOTE} type has deleted + */ +/** + * 当类型为{@link DataConstants#NOTE}的数据被删除时,更新笔记的内容 + */ + private static final String DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER = + "CREATE TRIGGER update_note_content_on_delete " + // 创建一个名为update_note_content_on_delete的触发器 + " AFTER delete ON " + TABLE.DATA +// 在数据表删除记录后触发 + " WHEN old." + DataColumns.MIME_TYPE + "='" + DataConstants.NOTE + "'" + // 当被删除记录的类型为笔记时 + " BEGIN" +// 触发器执行开始 + " UPDATE " + TABLE.NOTE +// 更新笔记表 + " SET " + NoteColumns.SNIPPET + "=''" +// 将笔记的摘要设置为空字符串 + " WHERE " + NoteColumns.ID + "=old." + DataColumns.NOTE_ID + ";" + // 条件是笔记ID与被删除记录的笔记ID相匹配 + " END";// 触发器执行结束 + + /** + * Delete datas belong to note which has been deleted + */ +/** + * 当笔记被删除时,删除属于该笔记的所有数据 + */ + private static final String NOTE_DELETE_DATA_ON_DELETE_TRIGGER = + "CREATE TRIGGER delete_data_on_delete " + + " AFTER DELETE ON " + TABLE.NOTE + + " BEGIN" + + " DELETE FROM " + TABLE.DATA + + " WHERE " + DataColumns.NOTE_ID + "=old." + NoteColumns.ID + ";" + + " END"; + + /** + * Delete notes belong to folder which has been deleted + */ + private static final String FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER = + "CREATE TRIGGER folder_delete_notes_on_delete " +// 创建一个名为delete_data_on_delete的触发器 + " AFTER DELETE ON " + TABLE.NOTE + // 在笔记表删除记录后触发 + " BEGIN" +// 触发器执行开始 + " DELETE FROM " + TABLE.NOTE + // 从数据表中删除记录 + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" + // 条件是数据记录的笔记ID与被删除笔记的ID相匹配 + " END"; // 触发器执行结束 + + /** + * Move notes belong to folder which has been moved to trash folder + */ +/** + * 创建一个触发器,当文件夹被移动到垃圾文件夹时,将属于该文件夹的笔记也移动到垃圾文件夹 + */ + private static final String FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER = + "CREATE TRIGGER folder_move_notes_on_trash " + // 创建名为folder_move_notes_on_trash的触发器 + " AFTER UPDATE ON " + TABLE.NOTE +// 在更新NOTE表后触发 + " WHEN new." + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 当新记录的PARENT_ID等于垃圾文件夹的ID时 + " BEGIN" + // 开始触发器执行的操作 + " UPDATE " + TABLE.NOTE +// 更新NOTE表 + " SET " + NoteColumns.PARENT_ID + "=" + Notes.ID_TRASH_FOLER + // 将PARENT_ID设置为垃圾文件夹的ID + " WHERE " + NoteColumns.PARENT_ID + "=old." + NoteColumns.ID + ";" +// 条件是PARENT_ID等于被更新记录的ID + " END";// 结束触发器 + + public NotesDatabaseHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION);// 调用父类构造函数,初始化数据库帮助器 + } + + public void createNoteTable(SQLiteDatabase db) { + db.execSQL(CREATE_NOTE_TABLE_SQL); // 执行创建笔记表的SQL语句 + reCreateNoteTableTriggers(db);// 重新创建笔记表相关的触发器 + createSystemFolder(db);// 创建系统文件夹 + Log.d(TAG, "note table has been created");// 在日志中记录笔记表已创建 + } + + private void reCreateNoteTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_update");// 如果存在increase_folder_count_on_update触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_update");// 如果存在decrease_folder_count_on_update触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS decrease_folder_count_on_delete");// 如果存在decrease_folder_count_on_delete触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS delete_data_on_delete");// 如果存在delete_data_on_delete触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS increase_folder_count_on_insert"); // 如果存在increase_folder_count_on_insert触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS folder_delete_notes_on_delete");// 如果存在folder_delete_notes_on_delete触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS folder_move_notes_on_trash");// 如果存在folder_move_notes_on_trash触发器,则删除 + + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);// 执行增加文件夹计数更新触发器的SQL语句 + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_UPDATE_TRIGGER);// 执行减少文件夹计数更新触发器的SQL语句 + db.execSQL(NOTE_DECREASE_FOLDER_COUNT_ON_DELETE_TRIGGER);// 执行减少文件夹计数删除触发器的SQL语句 + db.execSQL(NOTE_DELETE_DATA_ON_DELETE_TRIGGER); // 执行删除数据删除触发器的SQL语句 + db.execSQL(NOTE_INCREASE_FOLDER_COUNT_ON_INSERT_TRIGGER);// 执行增加文件夹计数插入触发器的SQL语句 + db.execSQL(FOLDER_DELETE_NOTES_ON_DELETE_TRIGGER); // 执行文件夹删除笔记删除触发器的SQL语句 + db.execSQL(FOLDER_MOVE_NOTES_ON_TRASH_TRIGGER);// 执行文件夹移动笔记到垃圾文件夹触发器的SQL语句 + } + + private void createSystemFolder(SQLiteDatabase db) { + ContentValues values = new ContentValues(); + + /** + * call record foler for call notes + */ + + /** + * 通话记录文件夹,用于通话笔记 + */ values.put(NoteColumns.ID, Notes.ID_CALL_RECORD_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * root folder which is default folder + */ + /** + * 根文件夹,是默认文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_ROOT_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * temporary folder which is used for moving note + */ + + /** + * 临时文件夹,用于移动笔记 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TEMPARAY_FOLDER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + + /** + * create trash folder + */ + /** + * 创建垃圾文件夹 + */ + values.clear(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } +/** + * 创建数据表,并重新创建相关触发器,同时创建数据表索引 + */ + public void createDataTable(SQLiteDatabase db) { + db.execSQL(CREATE_DATA_TABLE_SQL); // 执行创建数据表的SQL语句 + reCreateDataTableTriggers(db);// 重新创建数据表相关的触发器 + db.execSQL(CREATE_DATA_NOTE_ID_INDEX_SQL);// 执行创建数据表索引的SQL语句 + Log.d(TAG, "data table has been created");// 在日志中记录数据表已创建 + } +/** + * 重新创建数据表的触发器 + */ + private void reCreateDataTableTriggers(SQLiteDatabase db) { + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_insert"); // 如果存在update_note_content_on_insert触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_update"); // 如果存在update_note_content_on_update触发器,则删除 + db.execSQL("DROP TRIGGER IF EXISTS update_note_content_on_delete");// 如果存在update_note_content_on_delete触发器,则删除 + + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_INSERT_TRIGGER); // 执行插入时更新笔记内容的触发器 + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_UPDATE_TRIGGER); // 执行更新时更新笔记内容的触发器 + db.execSQL(DATA_UPDATE_NOTE_CONTENT_ON_DELETE_TRIGGER);// 执行删除时更新笔记内容的触发器 + } + + static synchronized NotesDatabaseHelper getInstance(Context context) {// 如果单例实例为空,则创建一个新的实例 + if (mInstance == null) { + mInstance = new NotesDatabaseHelper(context); + } // 返回单例实例 + return mInstance; + } +/** + * 当数据库被创建时调用此方法 + */ + @Override + public void onCreate(SQLiteDatabase db) { + createNoteTable(db);// 创建笔记表 + createDataTable(db); // 创建数据表 + } +/** + * 当数据库版本升级时调用此方法 + */ + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + boolean reCreateTriggers = false;// 是否需要重新创建触发器 + boolean skipV2 = false;// 是否跳过版本2的升级 + // 如果当前版本是1,则升级到版本2 + if (oldVersion == 1) { + upgradeToV2(db);// 执行版本2的升级操作 + skipV2 = true; // this upgrade including the upgrade from v2 to v3// 标记已处理版本2的升级,包括从版本2升级到版本3 + oldVersion++;// 更新当前版本号 + } + // 如果当前版本是2且没有跳过版本2的升级,则升级到版本3 + if (oldVersion == 2 && !skipV2) { + upgradeToV3(db);// 执行版本3的升级操作 + reCreateTriggers = true;// 标记需要重新创建触发器 + oldVersion++;// 更新当前版本号 + } + // 如果当前版本是3,则升级到版本4 + if (oldVersion == 3) { + upgradeToV4(db); // 执行版本4的升级操作 + oldVersion++;// 更新当前版本号 + } + // 如果需要重新创建触发器,则执行相关操作 + if (reCreateTriggers) { + reCreateNoteTableTriggers(db);// 重新创建笔记表的触发器 + reCreateDataTableTriggers(db);// 重新创建数据表的触发器 + } + // 如果当前版本号不等于新版本号,则抛出异常 + if (oldVersion != newVersion) { + throw new IllegalStateException("Upgrade notes database to version " + newVersion + + "fails"); + } + } +/** + * 将数据库升级到版本2 + */ + private void upgradeToV2(SQLiteDatabase db) { + db.execSQL("DROP TABLE IF EXISTS " + TABLE.NOTE); // 如果存在旧的笔记表,则删除 + db.execSQL("DROP TABLE IF EXISTS " + TABLE.DATA);// 如果存在旧的数据表,则删除 + createNoteTable(db); // 创建新的笔记表 + createDataTable(db); // 创建新的数据表 + } + +/** + * 将数据库升级到版本3 + */ + private void upgradeToV3(SQLiteDatabase db) { + // drop unused triggers + // 删除不再使用的触发器 + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_insert"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_delete"); + db.execSQL("DROP TRIGGER IF EXISTS update_note_modified_date_on_update"); + // add a column for gtask id + // 为笔记表添加一个用于Google Task ID的列 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.GTASK_ID + + " TEXT NOT NULL DEFAULT ''"); + // add a trash system folder + // 添加一个垃圾系统文件夹 + ContentValues values = new ContentValues(); + values.put(NoteColumns.ID, Notes.ID_TRASH_FOLER); + values.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + db.insert(TABLE.NOTE, null, values); + } +/** + * 将数据库升级到版本4 + */ + private void upgradeToV4(SQLiteDatabase db) { + // 为笔记表添加一个版本号列 + db.execSQL("ALTER TABLE " + TABLE.NOTE + " ADD COLUMN " + NoteColumns.VERSION + + " INTEGER NOT NULL DEFAULT 0"); + } +} diff --git a/NotesProvider.java b/NotesProvider.java new file mode 100644 index 0000000..053f860 --- /dev/null +++ b/NotesProvider.java @@ -0,0 +1,351 @@ +/* + * 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.data; + + +import android.app.SearchManager; +import android.content.ContentProvider; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Intent; +import android.content.UriMatcher; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; +import android.text.TextUtils; +import android.util.Log; + +import net.micode.notes.R; +import net.micode.notes.data.Notes.DataColumns; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.data.NotesDatabaseHelper.TABLE; + +/** + * 笔记内容提供者,用于提供笔记数据 + */ +public class NotesProvider extends ContentProvider { + // 用于匹配Uri的UriMatcher + private static final UriMatcher mMatcher; + // 数据库帮助类实例 + private NotesDatabaseHelper mHelper; + // 日志标签 + private static final String TAG = "NotesProvider"; + // Uri匹配码常量 + private static final int URI_NOTE = 1;// 匹配笔记列表 + private static final int URI_NOTE_ITEM = 2;; // 匹配单个笔记 + private static final int URI_DATA = 3;// 匹配数据列表 + private static final int URI_DATA_ITEM = 4;// 匹配单个数据项 + + private static final int URI_SEARCH = 5; // 匹配搜索结果 + private static final int URI_SEARCH_SUGGEST = 6;// 匹配搜索建议 + + static { // 初始化UriMatcher,用于匹配Uri + + mMatcher = new UriMatcher(UriMatcher.NO_MATCH); + // 添加Uri匹配规则 + mMatcher.addURI(Notes.AUTHORITY, "note", URI_NOTE);// 匹配笔记列表 + mMatcher.addURI(Notes.AUTHORITY, "note/#", URI_NOTE_ITEM);// 匹配单个笔记 + mMatcher.addURI(Notes.AUTHORITY, "data", URI_DATA);// 匹配数据列表 + mMatcher.addURI(Notes.AUTHORITY, "data/#", URI_DATA_ITEM);// 匹配单个数据项 + mMatcher.addURI(Notes.AUTHORITY, "search", URI_SEARCH);// 匹配搜索结果 + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY, URI_SEARCH_SUGGEST);// 匹配搜索建议 + mMatcher.addURI(Notes.AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", URI_SEARCH_SUGGEST); // 匹配带参数的搜索建议 + } + + /** + * x'0A' represents the '\n' character in sqlite. For title and content in the search result, + * we will trim '\n' and white space in order to show more information. + */ +/** + * NOTES_SEARCH_PROJECTION 定义了搜索结果中显示的列。 + * x'0A' 在sqlite中表示 '\n' 字符。为了在搜索结果中显示更多信息, + * 我们将标题和内容中的 '\n' 和空白字符进行修剪。 + */ + private static final String NOTES_SEARCH_PROJECTION = NoteColumns.ID + "," + + NoteColumns.ID + " AS " + SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_1 + "," + + "TRIM(REPLACE(" + NoteColumns.SNIPPET + ", x'0A','')) AS " + SearchManager.SUGGEST_COLUMN_TEXT_2 + "," + + R.drawable.search_result + " AS " + SearchManager.SUGGEST_COLUMN_ICON_1 + "," + + "'" + Intent.ACTION_VIEW + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_ACTION + "," + + "'" + Notes.TextNote.CONTENT_TYPE + "' AS " + SearchManager.SUGGEST_COLUMN_INTENT_DATA; +/** + * NOTES_SNIPPET_SEARCH_QUERY 定义了搜索笔记片段的查询语句。 + * 该查询从笔记表中选取符合条件的记录,并排除垃圾文件夹中的笔记。 + */ + private static String NOTES_SNIPPET_SEARCH_QUERY = "SELECT " + NOTES_SEARCH_PROJECTION + + " FROM " + TABLE.NOTE + + " WHERE " + NoteColumns.SNIPPET + " LIKE ?" + + " AND " + NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + + " AND " + NoteColumns.TYPE + "=" + Notes.TYPE_NOTE; +// 当ContentProvider被创建时调用 + @Override + public boolean onCreate() { + // 获取NotesDatabaseHelper的单例实例 + mHelper = NotesDatabaseHelper.getInstance(getContext()); + // 返回true表示ContentProvider初始化成功 + return true; + } +// 重写query方法以响应ContentProvider的查询请求 + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + Cursor c = null;// 初始化Cursor对象 + SQLiteDatabase db = mHelper.getReadableDatabase();// 获取可读数据库实例 + String id = null; // 初始化id变量 + // 根据Uri匹配结果执行不同的查询操作 + switch (mMatcher.match(uri)) { + case URI_NOTE:// 匹配笔记列表 + c = db.query(TABLE.NOTE, projection, selection, selectionArgs, null, null, + sortOrder);// 执行查询操作 + break; + case URI_NOTE_ITEM:// 匹配单个笔记 + id = uri.getPathSegments().get(1);// 从Uri中获取笔记ID + c = db.query(TABLE.NOTE, projection, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder); // 执行查询操作 + break; + case URI_DATA:// 匹配数据列表 + c = db.query(TABLE.DATA, projection, selection, selectionArgs, null, null, + sortOrder);// 执行查询操作 + break; + case URI_DATA_ITEM:// 匹配单个数据项 + id = uri.getPathSegments().get(1); // 从Uri中获取数据项ID + c = db.query(TABLE.DATA, projection, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs, null, null, sortOrder);// 执行查询操作 + break; + case URI_SEARCH:// 匹配搜索 + case URI_SEARCH_SUGGEST:// 匹配搜索建议 + // 如果提供了排序顺序或投影,则抛出异常 + if (sortOrder != null || projection != null) { + throw new IllegalArgumentException( + "do not specify sortOrder, selection, selectionArgs, or projection" + "with this query"); + } +// 初始化搜索字符串 + String searchString = null; + // 如果Uri匹配搜索建议 + if (mMatcher.match(uri) == URI_SEARCH_SUGGEST) { + // 如果Uri的路径段数量大于1,则获取搜索字符串 + if (uri.getPathSegments().size() > 1) { + searchString = uri.getPathSegments().get(1); + } + } else { + // 否则从Uri的查询参数中获取搜索字符串 + searchString = uri.getQueryParameter("pattern"); + } +// 如果搜索字符串为空,则返回null + if (TextUtils.isEmpty(searchString)) { + return null; + } +// 尝试执行搜索查询 + try { + // 格式化搜索字符串以进行模糊匹配 + searchString = String.format("%%%s%%", searchString); + // 执行原始查询,获取匹配的笔记片段 + c = db.rawQuery(NOTES_SNIPPET_SEARCH_QUERY, + new String[] { searchString }); + } catch (IllegalStateException ex) { + // 如果发生异常,记录错误日志 + Log.e(TAG, "got exception: " + ex.toString()); + }// 结束switch语句的当前分支 + break; + // 如果Uri不匹配已知情况,则抛出异常 + default: + throw new IllegalArgumentException("Unknown URI " + uri); + }// 如果Cursor不为空,设置通知Uri + if (c != null) { + c.setNotificationUri(getContext().getContentResolver(), uri); + }// 返回Cursor + return c; + } +// 重写insert方法以响应ContentProvider的插入请求 + @Override + public Uri insert(Uri uri, ContentValues values) { + SQLiteDatabase db = mHelper.getWritableDatabase(); + // 获取可写数据库实例 + long dataId = 0, noteId = 0, insertedId = 0; // 初始化ID变量 + // 根据Uri匹配结果执行不同的插入操作 + switch (mMatcher.match(uri)) { + case URI_NOTE: // 匹配笔记插入 + insertedId = noteId = db.insert(TABLE.NOTE, null, values);// 插入笔记并获取ID + break; + case URI_DATA:// 匹配数据插入 + // 检查ContentValues中是否包含笔记ID + if (values.containsKey(DataColumns.NOTE_ID)) {// 获取笔记ID + noteId = values.getAsLong(DataColumns.NOTE_ID); + } else { + // 如果没有笔记ID,记录调试信息 + Log.d(TAG, "Wrong data format without note id:" + values.toString()); + } + insertedId = dataId = db.insert(TABLE.DATA, null, values);// 插入数据并获取ID + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri);// 如果Uri不匹配已知情况,则抛出异常 + } + // Notify the note uri + // 如果插入的是笔记,则通知内容解析器变更 + if (noteId > 0) { + // 使用ContentUris.withAppendedId构建具体的笔记Uri,并通知变更 + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_NOTE_URI, noteId), null); + } + + // Notify the data uri + +// 如果插入的是数据,则通知内容解析器变更 + if (dataId > 0) { + // 使用ContentUris.withAppendedId构建具体的数据Uri,并通知变更 + getContext().getContentResolver().notifyChange( + ContentUris.withAppendedId(Notes.CONTENT_DATA_URI, dataId), null); + } +// 返回插入操作后带ID的Uri + return ContentUris.withAppendedId(uri, insertedId); + } +// 重写delete方法以响应ContentProvider的删除请求 + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + int count = 0;// 初始化删除计数器 + String id = null; // 初始化ID变量 + SQLiteDatabase db = mHelper.getWritableDatabase();// 获取可写数据库实例 + boolean deleteData = false; // 初始化是否删除数据的标志 + // 根据Uri匹配结果执行不同的删除操作 + switch (mMatcher.match(uri)) { + case URI_NOTE:// 匹配笔记删除 + // 添加条件确保只删除ID大于0的笔记 + selection = "(" + selection + ") AND " + NoteColumns.ID + ">0 "; + count = db.delete(TABLE.NOTE, selection, selectionArgs);// 执行删除操作 + break; + case URI_NOTE_ITEM:// 匹配特定笔记项删除 + id = uri.getPathSegments().get(1); // 获取笔记ID + // ID小于等于0的是系统文件夹,不允许删除到垃圾桶 + /** + * ID that smaller than 0 is system folder which is not allowed to + * trash + */ + long noteId = Long.valueOf(id); + if (noteId <= 0) { + break;// 如果是系统文件夹,则跳出 + } + count = db.delete(TABLE.NOTE,// 执行删除操作 + NoteColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + break; + case URI_DATA:// 匹配数据删除 + count = db.delete(TABLE.DATA, selection, selectionArgs);// 执行删除操作 + deleteData = true;// 设置删除数据标志 + break; + case URI_DATA_ITEM:// 匹配特定数据项删除 + id = uri.getPathSegments().get(1);// 获取数据ID + count = db.delete(TABLE.DATA,// 执行删除操作 + DataColumns.ID + "=" + id + parseSelection(selection), selectionArgs); + deleteData = true;// 设置删除数据标志 + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri); // 如果Uri不匹配已知情况,则抛出异常 + } // 如果删除了记录,则通知内容解析器变更 + if (count > 0) { + // 如果删除的是数据,则通知笔记Uri变更 + if (deleteData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + }// 通知原始Uri变更 + getContext().getContentResolver().notifyChange(uri, null); + }// 返回删除的记录数 + return count; + } +// 重写update方法以响应ContentProvider的更新请求 + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + int count = 0;// 初始化更新计数器 + String id = null;// 初始化ID变量 + SQLiteDatabase db = mHelper.getWritableDatabase(); // 获取可写数据库实例 + boolean updateData = false;// 初始化是否更新数据的标志 + // 根据Uri匹配结果执行不同的更新操作 + switch (mMatcher.match(uri)) { + case URI_NOTE:// 匹配笔记更新 + increaseNoteVersion(-1, selection, selectionArgs); // 更新笔记版本 + count = db.update(TABLE.NOTE, values, selection, selectionArgs);// 执行更新操作 + break; + case URI_NOTE_ITEM:// 匹配特定笔记项更新 + id = uri.getPathSegments().get(1); // 获取笔记ID + increaseNoteVersion(Long.valueOf(id), selection, selectionArgs);// 更新笔记版本 + count = db.update(TABLE.NOTE, values, NoteColumns.ID + "=" + id + + parseSelection(selection), selectionArgs);// 执行更新操作 + break; + case URI_DATA:// 匹配数据更新 + count = db.update(TABLE.DATA, values, selection, selectionArgs); // 执行更新操作 + updateData = true;// 设置更新数据标志 + break; + case URI_DATA_ITEM:// 匹配特定数据项更新 + id = uri.getPathSegments().get(1);// 获取数据ID + count = db.update(TABLE.DATA, values, DataColumns.ID + "=" + id + + parseSelection(selection), selectionArgs); // 执行更新操作 + updateData = true;// 设置更新数据标志 + break; + default: + throw new IllegalArgumentException("Unknown URI " + uri);// 如果Uri不匹配已知情况,则抛出异常 + } +// 如果更新了记录,则通知内容解析器变更 + if (count > 0) { + // 如果更新的是数据,则通知笔记Uri变更 + if (updateData) { + getContext().getContentResolver().notifyChange(Notes.CONTENT_NOTE_URI, null); + } // 通知原始Uri变更 + getContext().getContentResolver().notifyChange(uri, null); + } // 返回更新的记录数 + return count; + } +// 私有方法,用于解析选择条件 + private String parseSelection(String selection) { + // 如果选择条件不为空,则添加AND连接符,否则返回空字符串 + return (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""); + } +// 私有方法,用于增加笔记的版本号 + private void increaseNoteVersion(long id, String selection, String[] selectionArgs) { + StringBuilder sql = new StringBuilder(120); + // 创建StringBuilder对象用于构建SQL语句 + sql.append("UPDATE "); // 添加更新SQL语句的开始部分 + sql.append(TABLE.NOTE);// 指定更新的表 + sql.append(" SET ");// 添加设置字段的开始部分 + sql.append(NoteColumns.VERSION);// 指定要更新的版本字段 + sql.append("=" + NoteColumns.VERSION + "+1 "); // 将版本号加1 + // 如果提供了特定的ID或者有选择条件,则添加WHERE子句 + if (id > 0 || !TextUtils.isEmpty(selection)) { + sql.append(" WHERE "); + } + if (id > 0) { + // 如果提供了特定的ID,则将其作为更新条件 + sql.append(NoteColumns.ID + "=" + String.valueOf(id)); + } + if (!TextUtils.isEmpty(selection)) { + // 如果有选择条件,则处理选择条件和参数 + String selectString = id > 0 ? parseSelection(selection) : selection; + // 替换选择条件中的占位符为实际的参数值 + for (String args : selectionArgs) { + selectString = selectString.replaceFirst("\\?", args); + } + sql.append(selectString); + } + + // 执行构建的SQL语句以更新笔记版本 + mHelper.getWritableDatabase().execSQL(sql.toString()); + } +// 重写getType方法以响应ContentProvider的getType请求 + @Override + public String getType(Uri uri) { + // TODO Auto-generated method stub + // TODO: 根据Uri返回相应的MIME类型,此处需要实现具体的逻辑 + return null; + } + +} diff --git a/SqlData.java b/SqlData.java new file mode 100644 index 0000000..180cdc5 --- /dev/null +++ b/SqlData.java @@ -0,0 +1,200 @@ +/* + * 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. + */ +// SqlData类用于处理与GTasks相关的数据操作 +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; + + +public class SqlData { + // 日志标签,用于调试 + private static final String TAG = SqlData.class.getSimpleName(); +// 无效的ID常量 + private static final int INVALID_ID = -99999; + // 用于查询数据列的投影 + public static final String[] PROJECTION_DATA = new String[] { + DataColumns.ID, DataColumns.MIME_TYPE, DataColumns.CONTENT, DataColumns.DATA1, + DataColumns.DATA3 + }; +// 数据列的索引 + public static final int DATA_ID_COLUMN = 0; + + public static final int DATA_MIME_TYPE_COLUMN = 1; + + public static final int DATA_CONTENT_COLUMN = 2; + + public static final int DATA_CONTENT_DATA_1_COLUMN = 3; + + public static final int DATA_CONTENT_DATA_3_COLUMN = 4; + + private ContentResolver mContentResolver;// 内容解析器,用于与内容提供者交互 + + private boolean mIsCreate;// 标记是否为创建操作 + + private long mDataId; // 数据ID + + private String mDataMimeType; // 数据MIME类型 + private String mDataContent;// 数据内容 + + private long mDataContentData1; // 数据内容中的DATA1字段 + + private String mDataContentData3;// 数据内容中的DATA3字段 + + private ContentValues mDiffDataValues; // 用于存储数据差异的ContentValues +// SqlData类的构造函数和成员方法,用于处理GTasks数据 + public SqlData(Context context) { + mContentResolver = context.getContentResolver(); // 初始化内容解析器 + mIsCreate = true; // 标记为创建操作 + mDataId = INVALID_ID; // 设置无效的数据ID + mDataMimeType = DataConstants.NOTE; // 默认MIME类型为笔记 + mDataContent = "";// 默认内容为空字符串 + mDataContentData1 = 0; // 默认DATA1字段为0 + mDataContentData3 = ""; // 默认DATA3字段为空字符串 + mDiffDataValues = new ContentValues(); // 初始化用于存储数据差异的ContentValues + } +// 基于Cursor的构造函数,用于从Cursor加载数据 + public SqlData(Context context, Cursor c) { + mContentResolver = context.getContentResolver(); // 初始化内容解析器 + mIsCreate = false; // 标记为非创建操作 + loadFromCursor(c); // 从Cursor加载数据 + mDiffDataValues = new ContentValues(); // 初始化用于存储数据差异的ContentValues + } +// 私有方法,用于从Cursor加载数据到成员变量 + private void loadFromCursor(Cursor c) { + mDataId = c.getLong(DATA_ID_COLUMN); // 从Cursor获取数据ID + mDataMimeType = c.getString(DATA_MIME_TYPE_COLUMN); // 从Cursor获取MIME类型 + mDataContent = c.getString(DATA_CONTENT_COLUMN); // 从Cursor获取内容 + mDataContentData1 = c.getLong(DATA_CONTENT_DATA_1_COLUMN); // 从Cursor获取DATA1字段 + mDataContentData3 = c.getString(DATA_CONTENT_DATA_3_COLUMN); // 从Cursor获取DATA3字段 + } +// 公共方法,用于设置内容,接收一个JSONObject作为参数 + public void setContent(JSONObject js) throws JSONException { + long dataId = js.has(DataColumns.ID) ? js.getLong(DataColumns.ID) : INVALID_ID; // 从JSONObject获取数据ID,如果不存在则使用无效ID + // 如果是创建操作或者数据ID发生变化,则更新差异数据 + if (mIsCreate || mDataId != dataId) { + mDiffDataValues.put(DataColumns.ID, dataId); + } + mDataId = dataId;// 更新数据ID + // 从JSONObject获取MIME类型,如果不存在则使用默认笔记类型 + String dataMimeType = js.has(DataColumns.MIME_TYPE) ? js.getString(DataColumns.MIME_TYPE) + : DataConstants.NOTE; + // 如果是创建操作或者MIME类型发生变化,则更新差异数据 + if (mIsCreate || !mDataMimeType.equals(dataMimeType)) { + mDiffDataValues.put(DataColumns.MIME_TYPE, dataMimeType); + } + mDataMimeType = dataMimeType;// 更新MIME类型 + // 从JSONObject获取内容,如果不存在则使用空字符串 + String dataContent = js.has(DataColumns.CONTENT) ? js.getString(DataColumns.CONTENT) : ""; + // 如果是创建操作或者内容发生变化,则更新差异数据 + if (mIsCreate || !mDataContent.equals(dataContent)) { + mDiffDataValues.put(DataColumns.CONTENT, dataContent); + } // 更新内容 + mDataContent = dataContent; + // 从JSONObject获取DATA1字段,如果不存在则使用0 + long dataContentData1 = js.has(DataColumns.DATA1) ? js.getLong(DataColumns.DATA1) : 0; + // 如果是创建操作或者DATA1字段发生变化,则更新差异数据 + if (mIsCreate || mDataContentData1 != dataContentData1) { + mDiffDataValues.put(DataColumns.DATA1, dataContentData1); + } // 更新DATA1字段 + mDataContentData1 = dataContentData1;// 更新成员变量中的DATA1字段值 +// 从JSONObject获取DATA3字段,如果不存在则使用空字符串 + + String dataContentData3 = js.has(DataColumns.DATA3) ? js.getString(DataColumns.DATA3) : ""; + // 如果是创建操作或者DATA3字段值发生变化,则更新差异数据 + if (mIsCreate || !mDataContentData3.equals(dataContentData3)) {// 更新差异数据中的DATA3字段 + mDiffDataValues.put(DataColumns.DATA3, dataContentData3);// 更新成员变量中的DATA3字段值 + } + mDataContentData3 = dataContentData3; + } +// 公共方法,用于获取内容并返回一个JSONObject + public JSONObject getContent() throws JSONException { + // 如果是创建操作,则记录错误并返回null + if (mIsCreate) { + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } // 创建一个新的JSONObject并填充数据 + JSONObject js = new JSONObject(); + js.put(DataColumns.ID, mDataId); // 添加数据ID + js.put(DataColumns.MIME_TYPE, mDataMimeType);// 添加MIME类型 + js.put(DataColumns.CONTENT, mDataContent);// 添加内容 + js.put(DataColumns.DATA1, mDataContentData1);// 添加DATA1字段 + js.put(DataColumns.DATA3, mDataContentData3);// 添加DATA3字段 + return js; // 返回填充好的JSONObject + } +// 公共方法,用于提交更改到数据库 + public void commit(long noteId, boolean validateVersion, long version) { +// 如果是创建操作 + if (mIsCreate) { + // 如果数据ID无效且差异数据中包含ID,则移除ID + if (mDataId == INVALID_ID && mDiffDataValues.containsKey(DataColumns.ID)) { + mDiffDataValues.remove(DataColumns.ID); + } + + mDiffDataValues.put(DataColumns.NOTE_ID, noteId);// 添加笔记ID到差异数据 + Uri uri = mContentResolver.insert(Notes.CONTENT_DATA_URI, mDiffDataValues);// 插入新数据到数据库并获取Uri + // 尝试从Uri中获取新插入的数据ID + 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 + public long getId() { + return mDataId; + } +} diff --git a/SqlNote.java b/SqlNote.java new file mode 100644 index 0000000..8a60048 --- /dev/null +++ b/SqlNote.java @@ -0,0 +1,667 @@ +/* + * 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.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; + +public class SqlNote { + // 日志标签,用于调试 + private static final String TAG = SqlNote.class.getSimpleName(); + + // 定义一个无效的ID常量 + private static final int INVALID_ID = -99999; + + // 定义笔记查询的投影,即需要查询的列名数组 + 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, // 是否有附件 + 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, // GTasks 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; + + // 内容解析器 + private ContentResolver mContentResolver; + + // 是否为创建操作 + private boolean mIsCreate; + + // 笔记ID + private long mId; + + // 提醒日期 + private long mAlertDate; + + // 背景颜色ID + private int mBgColorId; + + // 创建日期 + private long mCreatedDate; + + // 是否有附件 + private int mHasAttachment; + + // 修改日期 + private long mModifiedDate; + + // 父笔记ID + private long mParentId; + + // 笔记摘要 + private String mSnippet; + + // 笔记类型 + private int mType; + + // 小部件ID + private int mWidgetId; + + // 小部件类型 + private int mWidgetType; + + // 原始父笔记ID + private long mOriginParent; + + // 笔记版本号 + private long mVersion; + + // 用于记录笔记差异的ContentValues + private ContentValues mDiffNoteValues; + + // SqlData对象列表,用于存储额外的数据 + private ArrayList mDataList; + +// 构造函数,用于创建新的SqlNote实例 + public SqlNote(Context context) { + + // 初始化上下文 + mContext = context; + + // 获取内容解析器 + mContentResolver = context.getContentResolver(); + + // 标记为创建操作 + mIsCreate = true; + + // 设置无效的ID + mId = INVALID_ID; + + // 初始化提醒日期 + mAlertDate = 0; + + // 获取默认背景颜色ID + mBgColorId = ResourceParser.getDefaultBgId(context); + + // 设置当前时间为创建日期 + mCreatedDate = System.currentTimeMillis(); + + // 初始化附件标记 + mHasAttachment = 0; + + // 设置当前时间为修改日期 + mModifiedDate = System.currentTimeMillis(); + + // 初始化父笔记ID + mParentId = 0; + + // 初始化笔记摘要 + mSnippet = ""; + + // 设置笔记类型为普通笔记 + mType = Notes.TYPE_NOTE; + + // 初始化小部件ID为无效ID + mWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; + + // 初始化小部件类型为无效类型 + mWidgetType = Notes.TYPE_WIDGET_INVALIDE; + + // 初始化原始父笔记ID + mOriginParent = 0; + + // 初始化版本号 + mVersion = 0; + + // 创建一个新的ContentValues对象用于记录差异 + mDiffNoteValues = new ContentValues(); + + // 创建一个新的SqlData列表 + mDataList = new ArrayList(); + + } +// 构造函数,用于从Cursor加载现有的SqlNote实例 + public SqlNote(Context context, Cursor c) { + + // 初始化上下文 + mContext = context; + + // 获取内容解析器 + mContentResolver = context.getContentResolver(); + + // 标记为非创建操作 + mIsCreate = false; + + // 从Cursor加载数据 + loadFromCursor(c); + + // 创建一个新的SqlData列表 + mDataList = new ArrayList(); + + // 如果笔记类型为普通笔记,则加载数据内容 + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + + // 创建一个新的ContentValues对象用于记录差异 + mDiffNoteValues = new ContentValues(); + } +// 构造函数,根据指定的ID创建SqlNote实例 + public SqlNote(Context context, long id) { + + // 初始化上下文 + mContext = context; + + // 获取内容解析器 + mContentResolver = context.getContentResolver(); + + // 标记为非创建操作 + mIsCreate = false; + + // 根据ID加载数据 + loadFromCursor(id); + + // 创建一个新的SqlData列表 + mDataList = new ArrayList(); + + // 如果笔记类型为普通笔记,则加载数据内容 + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + + // 创建一个新的ContentValues对象用于记录差异 + mDiffNoteValues = new ContentValues(); + + + } +// 根据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); + // 如果Cursor不为空,则移动到第一条记录 + if (c != null) { + c.moveToNext(); + loadFromCursor(c); // 从Cursor中加载数据 + } else {// 如果Cursor为空,则记录日志 + Log.w(TAG, "loadFromCursor: cursor = null"); + } + } finally { + // 关闭Cursor以释放资源 + if (c != null) + c.close(); + } + } + +// 从Cursor中加载数据到SqlNote实例 + private void loadFromCursor(Cursor c) { + // 从Cursor中获取笔记的各个字段值 + + // 从Cursor中获取ID并赋值给mId + mId = c.getLong(ID_COLUMN); + + // 从Cursor中获取提醒日期并赋值给mAlertDate + mAlertDate = c.getLong(ALERTED_DATE_COLUMN); + + // 从Cursor中获取背景颜色ID并赋值给mBgColorId + mBgColorId = c.getInt(BG_COLOR_ID_COLUMN); + + // 从Cursor中获取创建日期并赋值给mCreatedDate + mCreatedDate = c.getLong(CREATED_DATE_COLUMN); + + // 从Cursor中获取是否有附件的标记并赋值给mHasAttachment + mHasAttachment = c.getInt(HAS_ATTACHMENT_COLUMN); + + // 从Cursor中获取修改日期并赋值给mModifiedDate + mModifiedDate = c.getLong(MODIFIED_DATE_COLUMN); + + // 从Cursor中获取父笔记ID并赋值给mParentId + mParentId = c.getLong(PARENT_ID_COLUMN); + + // 从Cursor中获取笔记摘要并赋值给mSnippet + mSnippet = c.getString(SNIPPET_COLUMN); + + // 从Cursor中获取笔记类型并赋值给mType + mType = c.getInt(TYPE_COLUMN); + + // 从Cursor中获取小部件ID并赋值给mWidgetId + mWidgetId = c.getInt(WIDGET_ID_COLUMN); + + // 从Cursor中获取小部件类型并赋值给mWidgetType + mWidgetType = c.getInt(WIDGET_TYPE_COLUMN); + + // 从Cursor中获取笔记版本号并赋值给mVersion + mVersion = c.getLong(VERSION_COLUMN); + } +// 加载笔记的附加数据内容 + private void loadDataContent() { + Cursor c = null; + mDataList.clear(); // 清空现有的数据列表 + try { // 执行查询操作,获取与当前笔记相关的所有附加数据 + c = mContentResolver.query(Notes.CONTENT_DATA_URI, SqlData.PROJECTION_DATA, + "(note_id=?)", new String[] { + String.valueOf(mId) + }, null); + if (c != null) { // 如果Cursor不为空,且数据条目不为0 + if (c.getCount() == 0) { // 如果没有数据,记录日志并返回 + Log.w(TAG, "it seems that the note has not data"); + return; + } + while (c.moveToNext()) { // 遍历Cursor中的所有记录 + SqlData data = new SqlData(mContext, c); // 创建SqlData实例并从Cursor中加载数据 + mDataList.add(data); // 将SqlData实例添加到数据列表中 + } + } else { // 如果Cursor为空,记录日志 + Log.w(TAG, "loadDataContent: cursor = null"); + } + } finally { // 关闭Cursor以释放资源 + if (c != null) + c.close(); + } + } +// 设置笔记内容,返回是否设置成功 + public boolean setContent(JSONObject js) { + try { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); // 从JSONObject中获取笔记信息 + // 如果是系统文件夹,则不能设置内容 + if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + Log.w(TAG, "cannot set system folder"); + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // for folder we can only update the snnipet and type + // 对于文件夹,我们只能更新摘要和类型 + String snippet = note.has(NoteColumns.SNIPPET) ? note + .getString(NoteColumns.SNIPPET) : ""; + // 如果是新建或者摘要发生变化,则记录差异 + if (mIsCreate || !mSnippet.equals(snippet)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); + } + mSnippet = snippet; + + int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) + : Notes.TYPE_NOTE; + // 如果是新建或者类型发生变化,则记录差异 + if (mIsCreate || mType != type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); + } + mType = type; + } else if (note.getInt(NoteColumns.TYPE) == Notes.TYPE_NOTE) { + // 获取笔记的附加数据数组 + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + long id = note.has(NoteColumns.ID) ? note.getLong(NoteColumns.ID) : INVALID_ID; + // 如果是新建或者ID发生变化,则记录差异 + if (mIsCreate || mId != id) { + mDiffNoteValues.put(NoteColumns.ID, id); + } + mId = id; + + long alertDate = note.has(NoteColumns.ALERTED_DATE) ? note + .getLong(NoteColumns.ALERTED_DATE) : 0; + // 如果是新建或者提醒日期发生变化,则记录差异 + if (mIsCreate || mAlertDate != alertDate) { + mDiffNoteValues.put(NoteColumns.ALERTED_DATE, alertDate); + } + mAlertDate = alertDate; + + int bgColorId = note.has(NoteColumns.BG_COLOR_ID) ? note + .getInt(NoteColumns.BG_COLOR_ID) : ResourceParser.getDefaultBgId(mContext); + // 如果是新建或者背景颜色ID发生变化,则记录差异 + if (mIsCreate || mBgColorId != bgColorId) { + mDiffNoteValues.put(NoteColumns.BG_COLOR_ID, bgColorId); + } + mBgColorId = bgColorId;// 更新当前便签的背景颜色ID + // 获取便签的创建日期,如果便签包含创建日期则获取该日期,否则使用当前系统时间 + long createDate = note.has(NoteColumns.CREATED_DATE) ? note + .getLong(NoteColumns.CREATED_DATE) : System.currentTimeMillis(); + // 如果是新建便签或者创建日期有变化,则将变化记录到差异记录中 + if (mIsCreate || mCreatedDate != createDate) { + mDiffNoteValues.put(NoteColumns.CREATED_DATE, createDate); + } + mCreatedDate = createDate;// 更新当前便签的创建日期 + // 获取便签是否含有附件,如果便签包含该信息则获取,否则默认为无附件 + int hasAttachment = note.has(NoteColumns.HAS_ATTACHMENT) ? note + .getInt(NoteColumns.HAS_ATTACHMENT) : 0; + // 如果是新建便签或者是否含有附件的状态有变化,则将变化记录到差异记录中 + if (mIsCreate || mHasAttachment != hasAttachment) { + mDiffNoteValues.put(NoteColumns.HAS_ATTACHMENT, hasAttachment); + } + mHasAttachment = hasAttachment;// 更新当前便签是否含有附件的状态 + // 获取便签的最后修改日期,如果便签包含该信息则获取,否则使用当前系统时间 + long modifiedDate = note.has(NoteColumns.MODIFIED_DATE) ? note + .getLong(NoteColumns.MODIFIED_DATE) : System.currentTimeMillis(); + // 如果是新建便签或者最后修改日期有变化,则将变化记录到差异记录中 + if (mIsCreate || mModifiedDate != modifiedDate) { + mDiffNoteValues.put(NoteColumns.MODIFIED_DATE, modifiedDate); + } + mModifiedDate = modifiedDate;// 更新当前便签的最后修改日期 + // 获取便签的父级ID,如果便签包含该信息则获取,否则默认为无父级 + long parentId = note.has(NoteColumns.PARENT_ID) ? note + .getLong(NoteColumns.PARENT_ID) : 0; + // 如果是新建便签或者父级ID有变化,则将变化记录到差异记录中 + if (mIsCreate || mParentId != parentId) { + mDiffNoteValues.put(NoteColumns.PARENT_ID, parentId); + } + mParentId = parentId;// 更新当前便签的父级ID + + // 获取便签的摘要信息,如果便签包含该信息则获取,否则默认为空字符串 + String snippet = note.has(NoteColumns.SNIPPET) ? note + .getString(NoteColumns.SNIPPET) : ""; + // 如果是新建便签或者摘要信息有变化,则将变化记录到差异记录中 + if (mIsCreate || !mSnippet.equals(snippet)) { + mDiffNoteValues.put(NoteColumns.SNIPPET, snippet); + } // 此处代码不完整,下面应该继续记录摘要信息的差异 + mSnippet = snippet;// 更新当前便签的摘要信息 + // 获取便签的类型,如果便签包含类型信息则获取该类型,否则默认为普通便签类型 + int type = note.has(NoteColumns.TYPE) ? note.getInt(NoteColumns.TYPE) + : Notes.TYPE_NOTE; + // 如果是新建便签或者类型有变化,则将变化记录到差异记录中 + if (mIsCreate || mType != type) { + mDiffNoteValues.put(NoteColumns.TYPE, type); + } + mType = type;}// 更新当前便签的类型 + // 获取便签的桌面小部件ID,如果便签包含小部件ID则获取该ID,否则默认为无效小部件ID + int widgetId = note.has(NoteColumns.WIDGET_ID) ? note.getInt(NoteColumns.WIDGET_ID) + : AppWidgetManager.INVALID_APPWIDGET_ID; + // 如果是新建便签或者小部件ID有变化,则将变化记录到差异记录中 + if (mIsCreate || mWidgetId != widgetId) { + mDiffNoteValues.put(NoteColumns.WIDGET_ID, widgetId); + } + mWidgetId = widgetId;// 更新当前便签的桌面小部件ID + + // 获取便签的桌面小部件类型,如果便签包含小部件类型信息则获取该类型,否则默认为无效小部件类型 + int widgetType = note.has(NoteColumns.WIDGET_TYPE) ? note + .getInt(NoteColumns.WIDGET_TYPE) : Notes.TYPE_WIDGET_INVALIDE; + // 如果是新建便签或者小部件类型有变化,则将变化记录到差异记录中 + if (mIsCreate || mWidgetType != widgetType) { + mDiffNoteValues.put(NoteColumns.WIDGET_TYPE, widgetType); + } + mWidgetType = widgetType;// 更新当前便签的桌面小部件类型 + // 获取便签的原始父级ID,如果便签包含原始父级ID信息则获取该ID,否则默认为0 + long originParent = note.has(NoteColumns.ORIGIN_PARENT_ID) ? note + .getLong(NoteColumns.ORIGIN_PARENT_ID) : 0; + // 如果是新建便签或者原始父级ID有变化,则将变化记录到差异记录中 + if (mIsCreate || mOriginParent != originParent) { + mDiffNoteValues.put(NoteColumns.ORIGIN_PARENT_ID, originParent); + } + mOriginParent = originParent;// 更新当前便签的原始父级ID + // 遍历数据数组,处理每个JSON对象 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); + SqlData sqlData = null; + // 如果JSON对象包含ID字段,则尝试从数据列表中找到对应的数据对象 + if (data.has(DataColumns.ID)) { + long dataId = data.getLong(DataColumns.ID); + for (SqlData temp : mDataList) { + if (dataId == temp.getId()) { + sqlData = temp; + } + } + } + // 如果没有找到对应的数据对象,则创建一个新的数据对象并添加到数据列表中 + if (sqlData == null) { + sqlData = new SqlData(mContext); + mDataList.add(sqlData); + } + + sqlData.setContent(data);// 设置sqlData的内容 + } + } + } catch (JSONException e) { + Log.e(TAG, e.toString());// 打印错误日志 + e.printStackTrace();// 打印异常堆栈信息 + return false;// 返回处理失败 + } + return true;// 返回处理成功 + } +// 获取内容的方法 + public JSONObject getContent() { + try { + JSONObject js = new JSONObject(); // 创建一个新的JSONObject + // 检查是否已经在数据库中创建 + if (mIsCreate) { // 如果没有创建,打印错误日志并返回null + Log.e(TAG, "it seems that we haven't created this in database yet"); + return null; + } + // 创建一个新的JSONObject用于存储笔记信息 + JSONObject note = new JSONObject(); + // 检查笔记类型 + if (mType == Notes.TYPE_NOTE) { + // 将笔记信息添加到note对象中 + note.put(NoteColumns.ID, mId); + note.put(NoteColumns.ALERTED_DATE, mAlertDate); + note.put(NoteColumns.BG_COLOR_ID, mBgColorId); + note.put(NoteColumns.CREATED_DATE, mCreatedDate); + note.put(NoteColumns.HAS_ATTACHMENT, mHasAttachment); + note.put(NoteColumns.MODIFIED_DATE, mModifiedDate); + note.put(NoteColumns.PARENT_ID, mParentId); + note.put(NoteColumns.SNIPPET, mSnippet); + note.put(NoteColumns.TYPE, mType); + note.put(NoteColumns.WIDGET_ID, mWidgetId); + note.put(NoteColumns.WIDGET_TYPE, mWidgetType); + note.put(NoteColumns.ORIGIN_PARENT_ID, mOriginParent); + // 将note对象添加到js对象中 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); + // 创建一个新的JSONArray用于存储数据 + JSONArray dataArray = new JSONArray(); + // 遍历mDataList列表 + for (SqlData sqlData : mDataList) { + JSONObject data = sqlData.getContent(); // 获取sqlData的内容 + if (data != null) { + dataArray.put(data); // 如果内容不为空,则添加到dataArray中 + } // 将dataArray添加到js对象中 + } + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); + } else if (mType == Notes.TYPE_FOLDER || mType == Notes.TYPE_SYSTEM) { // 如果类型是文件夹或系统,则只添加ID到note对象中 + note.put(NoteColumns.ID, mId); // 添加ID到note对象 + note.put(NoteColumns.TYPE, mType); // 添加类型到note对象 + note.put(NoteColumns.SNIPPET, mSnippet); // 添加片段信息到note对象 + js.put(GTaskStringUtils.META_HEAD_NOTE, note); // 将note对象添加到js对象中 + } + + return js;// 返回构建的JSONObject + } catch (JSONException e) { + Log.e(TAG, e.toString()); // 打印错误日志 + e.printStackTrace(); // 打印异常堆栈信息 + } + return null;// 如果发生异常,则返回null + } +// 设置父ID的方法 + public void setParentId(long id) { + mParentId = id; // 更新父ID + mDiffNoteValues.put(NoteColumns.PARENT_ID, id); // 在差异值集合中设置父ID + } + +// 设置GtaskID的方法 + public void setGtaskId(String gid) { + mDiffNoteValues.put(NoteColumns.GTASK_ID, gid); // 在差异值集合中设置GtaskID + } +// 设置同步ID的方法 + public void setSyncId(long syncId) { + mDiffNoteValues.put(NoteColumns.SYNC_ID, syncId); // 在差异值集合中设置同步ID + } + +// 重置本地修改状态的方法 + public void resetLocalModified() { + mDiffNoteValues.put(NoteColumns.LOCAL_MODIFIED, 0); // 在差异值集合中将本地修改状态设置为0 + } +// 获取ID的方法 + public long getId() { + return mId; // 返回ID + } +// 获取父ID的方法 + public long getParentId() { + return mParentId; // 返回父ID + } +// 获取片段信息的方法 + public String getSnippet() { + return mSnippet; // 返回片段信息 + } +// 检查是否是笔记类型的方法 + public boolean isNoteType() { + return mType == Notes.TYPE_NOTE; // 如果类型是笔记,则返回true + } +// 提交更改到数据库的方法 + public void commit(boolean validateVersion) { + // 如果是创建操作 + if (mIsCreate) { + // 如果ID无效且差异值集合中包含ID,则移除ID + if (mId == INVALID_ID && mDiffNoteValues.containsKey(NoteColumns.ID)) { + mDiffNoteValues.remove(NoteColumns.ID); + } + // 向数据库插入新的笔记记录,并获取返回的URI + Uri uri = mContentResolver.insert(Notes.CONTENT_NOTE_URI, mDiffNoteValues); + try { // 从URI中解析出笔记的ID + mId = Long.valueOf(uri.getPathSegments().get(1)); + } catch (NumberFormatException e) {、 + // 如果解析ID失败,记录错误日志并抛出异常 + Log.e(TAG, "Get note id error :" + e.toString()); + throw new ActionFailureException("create note failed"); + } + // 如果ID为0,则抛出异常 + if (mId == 0) { + throw new IllegalStateException("Create thread id failed"); + } + + // 如果类型是笔记,则提交所有关联的SqlData + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, false, -1); + } + } + } else { // 如果不是创建操作,检查ID是否有效 + if (mId <= 0 && mId != Notes.ID_ROOT_FOLDER && mId != Notes.ID_CALL_RECORD_FOLDER) { + Log.e(TAG, "No such note"); + throw new IllegalStateException("Try to update note with invalid id"); + } // 如果差异值集合中有更改,则更新版本号并执行更新操作 + if (mDiffNoteValues.size() > 0) { + mVersion ++; + int result = 0; + // 如果不需要验证版本,则直接更新 + if (!validateVersion) { + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?)", new String[] { + String.valueOf(mId) + }); + } else { // 如果需要验证版本,则添加版本号的条件 + result = mContentResolver.update(Notes.CONTENT_NOTE_URI, mDiffNoteValues, "(" + + NoteColumns.ID + "=?) AND (" + NoteColumns.VERSION + "<=?)", + new String[] { + String.valueOf(mId), String.valueOf(mVersion) + }); + } + if (result == 0) {// 如果更新结果为0,表示没有更新任何记录,可能是用户在同步时更新了笔记 + Log.w(TAG, "there is no update. maybe user updates note when syncing"); + } + } +// 如果类型是笔记,则提交所有关联的SqlData + if (mType == Notes.TYPE_NOTE) { + for (SqlData sqlData : mDataList) { + sqlData.commit(mId, validateVersion, mVersion); // 对每个SqlData执行提交操作,传入笔记ID、是否验证版本和当前版本号 + } + } + } + + // refresh local info + loadFromCursor(mId);// 刷新本地信息 + + // 如果类型是笔记,则加载数据内容 + if (mType == Notes.TYPE_NOTE) + loadDataContent(); + + mDiffNoteValues.clear();// 清空差异值集合 + mIsCreate = false;// 标记为非创建状态 + } +} diff --git a/Task.java b/Task.java new file mode 100644 index 0000000..0897e05 --- /dev/null +++ b/Task.java @@ -0,0 +1,411 @@ +/* + * 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.database.Cursor; +import android.text.TextUtils; +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.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +// Task类继承自Node类,表示一个任务节点 +public class Task extends Node { + // 类的标记,用于日志输出 + private static final String TAG = Task.class.getSimpleName(); + // 表示任务是否完成 + private boolean mCompleted; + // 任务备注 + private String mNotes; + // 任务的元信息 + private JSONObject mMetaInfo; + // 当前任务的前一个兄弟节点 + private Task mPriorSibling; + + // 当前任务的父任务列表 + private TaskList mParent; + // 无参构造方法 + public Task() { + super(); + mCompleted = false;// 初始化时任务未完成 + mNotes = null;// 备注 + mPriorSibling = null;// 前一个兄弟节点 + mParent = null;// 父任务列表 + mMetaInfo = null; // 元信息 + } + // 获取创建任务的JSON动作对象 + public JSONObject getCreateAction(int actionId) { + JSONObject js = new JSONObject();;// 创建一个新的JSON对象 + + try { + // action_type // 设置动作类型为创建 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // action_id // 设置动作ID + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // index // 设置任务在父列表中的索引 + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mParent.getChildTaskIndex(this)); + + // entity_delta // 设置实体变化信息 + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());// 任务名称 + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null");// 创建者ID + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_TASK);// 实体类型为任务 + if (getNotes() != null) {// 如果获取到的备注信息不为空,则将备注信息添加到实体变化信息中 + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes());// 任务备注 + } + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity);// 将实体变化信息放入动作对象 + + // parent_id // 设置父任务ID + js.put(GTaskStringUtils.GTASK_JSON_PARENT_ID, mParent.getGid());// 获取父任务列表的GID并设置为父任务ID + + // dest_parent_type// 设置目标父类型为任务组 + js.put(GTaskStringUtils.GTASK_JSON_DEST_PARENT_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP);// 指定目标父类型为任务组 + + // list_id// 设置列表ID,这里同样使用父任务列表的GID + js.put(GTaskStringUtils.GTASK_JSON_LIST_ID, mParent.getGid());// 获取父任务列表的GID并设置为列表ID + + // prior_sibling_id// 如果存在前一个兄弟节点,则设置前一个兄弟节点的ID + if (mPriorSibling != null) { + js.put(GTaskStringUtils.GTASK_JSON_PRIOR_SIBLING_ID, mPriorSibling.getGid());// 获取前一个兄弟节点的GID并设置为前一个兄弟节点ID + } +// 如果在创建JSON对象过程中发生异常,则捕获异常并记录日志 + } catch (JSONException e) { // 如果发生JSON异常,则输出日志 + Log.e(TAG, e.toString());// 输出异常信息到日志 + e.printStackTrace();// 打印异常堆栈信息 + throw new ActionFailureException("fail to generate task-create jsonobject");// 抛出操作失败异常 + } + + return js;// 返回创建的JSON动作对象 + } +// 获取更新任务的JSON动作对象 + public JSONObject getUpdateAction(int actionId) { + JSONObject js = new JSONObject();// 创建一个新的JSON对象 + + try { + // action_type // 设置动作类型为更新 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // action_id // 设置动作ID + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // id // 设置任务的ID + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid());// 获取当前任务的GID并设置为ID + + // entity_delta // 设置实体变化信息 + JSONObject entity = new JSONObject(); + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName());// 更新任务名称 + if (getNotes() != null) { + entity.put(GTaskStringUtils.GTASK_JSON_NOTES, getNotes()); // 更新任务备注 + } + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted());// 更新任务是否被删除的状态 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); // 将实体变化信息放入动作对象 + // 如果在创建JSON对象过程中发生异常,则捕获异常并记录日志 + } catch (JSONException e) { + Log.e(TAG, e.toString());// 输出异常信息到日志 + e.printStackTrace();// 打印异常堆栈信息 + throw new ActionFailureException("fail to generate task-update jsonobject");// 抛出操作失败异常 + } + + return js; // 返回创建的JSON动作对象 + } +/** + * 根据远程JSON对象设置任务内容 + * @param js 远程JSON对象 + */ + public void setContentByRemoteJSON(JSONObject js) { + if (js != null) { // 如果JSON对象不为空 + try { + // id // 设置任务ID + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // last_modified // 设置最后修改时间 + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // name // 设置任务名称 + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + // notes // 设置任务备注 + if (js.has(GTaskStringUtils.GTASK_JSON_NOTES)) { + setNotes(js.getString(GTaskStringUtils.GTASK_JSON_NOTES)); + } + + // deleted // 设置任务是否被删除 + if (js.has(GTaskStringUtils.GTASK_JSON_DELETED)) { + setDeleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_DELETED)); + } + + // completed // 设置任务是否已完成 + if (js.has(GTaskStringUtils.GTASK_JSON_COMPLETED)) { + setCompleted(js.getBoolean(GTaskStringUtils.GTASK_JSON_COMPLETED)); + } + } catch (JSONException e) { + Log.e(TAG, e.toString());// 记录异常信息到日志 + e.printStackTrace(); // 打印异常堆栈信息 + throw new ActionFailureException("fail to get task content from jsonobject");// 抛出操作失败异常 + } + } + } +/** + * 根据本地JSON对象设置任务内容 + * @param js 本地JSON对象 + */ + public void setContentByLocalJSON(JSONObject js) { + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE) + || !js.has(GTaskStringUtils.META_HEAD_DATA)) { + Log.w(TAG, "setContentByLocalJSON: nothing is avaiable"); + } + + try { + JSONObject note = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE);// 获取note部分的JSON对象 + JSONArray dataArray = js.getJSONArray(GTaskStringUtils.META_HEAD_DATA);// 获取data部分的JSONArray + + if (note.getInt(NoteColumns.TYPE) != Notes.TYPE_NOTE) {// 如果note的类型不是笔记类型 + Log.e(TAG, "invalid type"); // 记录错误信息到日志 + return;// 结束方法执行 + } + // 遍历dataArray数组 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i); // 获取数组中的每个JSONObject + // 如果数据类型是笔记 + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) {// 设置任务名称 + setName(data.getString(DataColumns.CONTENT));// 设置任务名称 + break;// 找到匹配的数据后跳出循环 + } + } + + } catch (JSONException e) { + Log.e(TAG, e.toString());// 记录异常信息到日志 + e.printStackTrace();// 打印异常堆栈信息 + } + } + +/** + * 从内容中获取本地JSON对象 + * @return 本地JSON对象 + */ + public JSONObject getLocalJSONFromContent() { + String name = getName();// 获取任务名称 + try { // 如果mMetaInfo为空,表示是新从网页创建的任务 + if (mMetaInfo == null) { + // new task created from web + if (name == null) {// 如果任务名称为空 + Log.w(TAG, "the note seems to be an empty one"); // 记录警告信息到日志 + return null;// 返回null + } + + JSONObject js = new JSONObject();// 创建新的JSONObject + JSONObject note = new JSONObject(); // 创建note部分的JSONObject + JSONArray dataArray = new JSONArray();// 创建data部分的JSONArray + JSONObject data = new JSONObject(); // 创建data部分的JSONObject + data.put(DataColumns.CONTENT, name); // 将任务名称放入data + dataArray.put(data); // 将data放入dataArray + js.put(GTaskStringUtils.META_HEAD_DATA, dataArray); // 将dataArray放入js + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE);// 设置note的类型 + js.put(GTaskStringUtils.META_HEAD_NOTE, note);// 将note放入js + return js;// 返回创建的JSON对象 + } else { + // synced task // 如果mMetaInfo不为空,表示是已同步的任务 + JSONObject note = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + // 获取已同步任务的data部分 + JSONArray dataArray = mMetaInfo.getJSONArray(GTaskStringUtils.META_HEAD_DATA); + // 遍历dataArray数组 + for (int i = 0; i < dataArray.length(); i++) { + JSONObject data = dataArray.getJSONObject(i);// 获取数组中的每个JSONObject + // 如果数据类型是笔记 + if (TextUtils.equals(data.getString(DataColumns.MIME_TYPE), DataConstants.NOTE)) { + data.put(DataColumns.CONTENT, getName());// 更新笔记内容 + break; // 找到匹配的数据后跳出循环 + } + } + // 更新note的类型 + note.put(NoteColumns.TYPE, Notes.TYPE_NOTE); + return mMetaInfo; // 返回更新后的mMetaInfo + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); // 记录异常信息到日志 + e.printStackTrace();// 打印异常堆栈信息 + return null;// 发生异常时返回null + } + } + +/** + * 设置元数据信息 + * @param metaData 元数据对象 + */ + public void setMetaInfo(MetaData metaData) { + if (metaData != null && metaData.getNotes() != null) { // 如果元数据对象不为空且包含笔记信息 + try { + mMetaInfo = new JSONObject(metaData.getNotes()); // 将笔记信息转换为JSONObject + } catch (JSONException e) { + Log.w(TAG, e.toString()); // 记录警告信息到日志 + mMetaInfo = null;// 发生异常时将mMetaInfo设置为null + } + } + } +/** + * 获取同步操作类型 + * @param c 数据库游标 + * @return 同步操作类型 + */ + public int getSyncAction(Cursor c) { + try { + JSONObject noteInfo = null; // 如果mMetaInfo不为空并且包含 + if (mMetaInfo != null && mMetaInfo.has(GTaskStringUtils.META_HEAD_NOTE)) { + noteInfo = mMetaInfo.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + } + // 如果noteInfo为空,表示笔记元数据可能已被删除 + if (noteInfo == null) { + Log.w(TAG, "it seems that note meta has been deleted"); + return SYNC_ACTION_UPDATE_REMOTE;// 需要更新远程数据 + } + // 如果noteInfo不包含ID字段,表示远程笔记ID可能已被删除 + if (!noteInfo.has(NoteColumns.ID)) { + Log.w(TAG, "remote note id seems to be deleted"); + return SYNC_ACTION_UPDATE_LOCAL; // 需要更新本地数据 + } + + // validate the note id now // 验证笔记ID是否匹配 + if (c.getLong(SqlNote.ID_COLUMN) != noteInfo.getLong(NoteColumns.ID)) { + Log.w(TAG, "note id doesn't match"); + return SYNC_ACTION_UPDATE_LOCAL;// 笔记ID不匹配,需要更新本地数据 + } + + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // there is no local update + // 如果本地没有修改 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // no update both side // 如果本地和远程最后修改时间相同,则无需同步 + return SYNC_ACTION_NONE; // 无需同步 + } else { + // apply remote to local // 需要将远程数据应用到本地 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // validate gtask id // 验证gtask id是否匹配 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR;// gtask id不匹配,同步错误 + } // 如果本地修改时间等于远程最后修改时间,则只需更新远程 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // local modification only + return SYNC_ACTION_UPDATE_REMOTE;// 只需更新远程 + } else { // 存在冲突,需要解决冲突 + return SYNC_ACTION_UPDATE_CONFLICT; + } + } + } catch (Exception e) { + Log.e(TAG, e.toString());// 记录异常信息到日志 + e.printStackTrace();// 打印异常堆栈信息 + } + + return SYNC_ACTION_ERROR; // 如果发生异常,返回同步错误 + } + +/** + * 判断任务是否值得保存 + * @return 如果元信息不为空,或者任务名称不为空且长度大于0,或者任务备注不为空且长度大于0,则返回true + */ +public boolean isWorthSaving() { + return mMetaInfo != null || (getName() != null && getName().trim().length() > 0) + || (getNotes() != null && getNotes().trim().length() > 0); +} + +/** + * 设置任务是否完成 + * @param completed 是否完成的标志 + */ +public void setCompleted(boolean completed) { + this.mCompleted = completed; +} + +/** + * 设置任务备注 + * @param notes 任务备注 + */ +public void setNotes(String notes) { + this.mNotes = notes; +} + +/** + * 设置前一个兄弟任务 + * @param priorSibling 前一个兄弟任务 + */ +public void setPriorSibling(Task priorSibling) { + this.mPriorSibling = priorSibling; +} + +/** + * 设置父任务列表 + * @param parent 父任务列表 + */ +public void setParent(TaskList parent) { + this.mParent = parent; +} + +/** + * 获取任务是否完成的标志 + * @return 是否完成 + */ +public boolean getCompleted() { + return this.mCompleted; +} + +/** + * 获取任务备注 + * @return 任务备注 + */ +public String getNotes() { + return this.mNotes; +} + +/** + * 获取前一个兄弟任务 + * @return 前一个兄弟任务 + */ +public Task getPriorSibling() { + return this.mPriorSibling; +} + +/** + * 获取父任务列表 + * @return 父任务列表 + */ +public TaskList getParent() { + return this.mParent; +} + +} diff --git a/TaskList.java b/TaskList.java new file mode 100644 index 0000000..03c6ff9 --- /dev/null +++ b/TaskList.java @@ -0,0 +1,463 @@ +/* + * 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.database.Cursor; +import android.util.Log; + +import net.micode.notes.data.Notes; +import net.micode.notes.data.Notes.NoteColumns; +import net.micode.notes.gtask.exception.ActionFailureException; +import net.micode.notes.tool.GTaskStringUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; + + +public class TaskList extends Node { + // 类标签,用于日志输出 + private static final String TAG = TaskList.class.getSimpleName(); + + // 任务列表的索引 + private int mIndex; + + // 子任务列表 + private ArrayList mChildren; + + // 构造方法 + public TaskList() { + super(); + // 初始化子任务列表 + mChildren = new ArrayList(); + // 设置默认索引值为1 + mIndex = 1; + } + + // 获取创建操作的JSON对象 + public JSONObject getCreateAction(int actionId) { + // 创建JSON对象 + JSONObject js = new JSONObject(); + + try { + // 添加操作类型 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_CREATE); + + // 添加操作ID + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 添加索引 + js.put(GTaskStringUtils.GTASK_JSON_INDEX, mIndex); + + // 添加实体变更信息 + JSONObject entity = new JSONObject(); + // 添加名称 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 添加创建者ID,此处为"null" + entity.put(GTaskStringUtils.GTASK_JSON_CREATOR_ID, "null"); + // 添加实体类型 + entity.put(GTaskStringUtils.GTASK_JSON_ENTITY_TYPE, + GTaskStringUtils.GTASK_JSON_TYPE_GROUP); + // 将实体变更信息添加到主JSON对象 + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + // 日志记录异常 + Log.e(TAG, e.toString()); + // 打印堆栈跟踪 + e.printStackTrace(); + // 抛出操作失败异常 + throw new ActionFailureException("fail to generate tasklist-create jsonobject"); + } + + // 返回创建操作的JSON对象 + return js; + } +} + + + + + + +public JSONObject getUpdateAction(int actionId) { + // 创建一个空的JSONObject对象 + JSONObject js = new JSONObject(); + + try { + // 添加操作类型,表示这是一个更新操作 + js.put(GTaskStringUtils.GTASK_JSON_ACTION_TYPE, + GTaskStringUtils.GTASK_JSON_ACTION_TYPE_UPDATE); + + // 添加操作ID + js.put(GTaskStringUtils.GTASK_JSON_ACTION_ID, actionId); + + // 添加任务列表的ID + js.put(GTaskStringUtils.GTASK_JSON_ID, getGid()); + + // 创建一个包含实体变更信息的JSONObject + JSONObject entity = new JSONObject(); + // 添加任务列表的名称 + entity.put(GTaskStringUtils.GTASK_JSON_NAME, getName()); + // 添加任务列表的删除状态 + entity.put(GTaskStringUtils.GTASK_JSON_DELETED, getDeleted()); + // 将实体变更信息添加到主JSONObject + js.put(GTaskStringUtils.GTASK_JSON_ENTITY_DELTA, entity); + + } catch (JSONException e) { + // 记录错误日志 + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出操作失败异常 + throw new ActionFailureException("fail to generate tasklist-update jsonobject"); + } + + // 返回包含更新操作的JSONObject + return js; +} + + + +public void setContentByRemoteJSON(JSONObject js) { + // 检查传入的JSONObject是否为null + if (js != null) { + try { + // 检查JSONObject中是否包含id字段,并设置gid + if (js.has(GTaskStringUtils.GTASK_JSON_ID)) { + setGid(js.getString(GTaskStringUtils.GTASK_JSON_ID)); + } + + // 检查JSONObject中是否包含last_modified字段,并设置最后修改时间 + if (js.has(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)) { + setLastModified(js.getLong(GTaskStringUtils.GTASK_JSON_LAST_MODIFIED)); + } + + // 检查JSONObject中是否包含name字段,并设置名称 + if (js.has(GTaskStringUtils.GTASK_JSON_NAME)) { + setName(js.getString(GTaskStringUtils.GTASK_JSON_NAME)); + } + + } catch (JSONException e) { + // 记录错误日志 + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 抛出操作失败异常 + throw new ActionFailureException("fail to get tasklist content from jsonobject"); + } + } +} + + + + + +public void setContentByLocalJSON(JSONObject js) { + // 检查传入的JSONObject是否为null或者不包含META_HEAD_NOTE字段 + if (js == null || !js.has(GTaskStringUtils.META_HEAD_NOTE)) { + // 记录警告日志 + Log.w(TAG, "setContentByLocalJSON: nothing is available"); + } + + try { + // 从JSONObject中获取META_HEAD_NOTE对象 + JSONObject folder = js.getJSONObject(GTaskStringUtils.META_HEAD_NOTE); + + // 检查folder的类型是否为文件夹 + if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_FOLDER) { + // 获取文件夹名称,并设置当前对象的名称 + String name = folder.getString(NoteColumns.SNIPPET); + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + name); + } else if (folder.getInt(NoteColumns.TYPE) == Notes.TYPE_SYSTEM) { + // 检查系统文件夹类型,并根据ID设置名称 + if (folder.getLong(NoteColumns.ID) == Notes.ID_ROOT_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_DEFAULT); + else if (folder.getLong(NoteColumns.ID) == Notes.ID_CALL_RECORD_FOLDER) + setName(GTaskStringUtils.MIUI_FOLDER_PREFFIX + GTaskStringUtils.FOLDER_CALL_NOTE); + else + // 记录错误日志,无效的系统文件夹 + Log.e(TAG, "invalid system folder"); + } else { + // 记录错误日志,错误的类型 + Log.e(TAG, "error type"); + } + } catch (JSONException e) { + // 记录错误日志 + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } +} + + + + + +public JSONObject getLocalJSONFromContent() { + try { + // 创建一个新的JSONObject + JSONObject js = new JSONObject(); + // 创建一个用于存储文件夹信息的JSONObject + JSONObject folder = new JSONObject(); + + // 获取当前对象的名称 + String folderName = getName(); + // 如果名称以特定前缀开始,则去除前缀 + if (getName().startsWith(GTaskStringUtils.MIUI_FOLDER_PREFFIX)) + folderName = folderName.substring(GTaskStringUtils.MIUI_FOLDER_PREFFIX.length(), + folderName.length()); + // 将处理后的文件夹名称放入folder对象 + folder.put(NoteColumns.SNIPPET, folderName); + // 根据文件夹名称判断是否为系统文件夹,并设置类型 + if (folderName.equals(GTaskStringUtils.FOLDER_DEFAULT) + || folderName.equals(GTaskStringUtils.FOLDER_CALL_NOTE)) + folder.put(NoteColumns.TYPE, Notes.TYPE_SYSTEM); + else + folder.put(NoteColumns.TYPE, Notes.TYPE_FOLDER); + + // 将folder对象放入js对象中 + js.put(GTaskStringUtils.META_HEAD_NOTE, folder); + + // 返回构建好的JSONObject + return js; + } catch (JSONException e) { + // 记录错误日志 + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + // 发生异常时返回null + return null; + } +} + + + +public int getSyncAction(Cursor c) { + try { + // 检查是否有本地修改 + if (c.getInt(SqlNote.LOCAL_MODIFIED_COLUMN) == 0) { + // 没有本地更新 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 双方都没有更新 + return SYNC_ACTION_NONE; + } else { + // 应用远程数据到本地 + return SYNC_ACTION_UPDATE_LOCAL; + } + } else { + // 验证gtask id是否匹配 + if (!c.getString(SqlNote.GTASK_ID_COLUMN).equals(getGid())) { + // gtask id不匹配,记录错误日志 + Log.e(TAG, "gtask id doesn't match"); + return SYNC_ACTION_ERROR; + } + // 检查同步ID是否与最后修改时间一致 + if (c.getLong(SqlNote.SYNC_ID_COLUMN) == getLastModified()) { + // 只有本地修改 + return SYNC_ACTION_UPDATE_REMOTE; + } else { + // 对于文件夹冲突,仅应用本地修改 + return SYNC_ACTION_UPDATE_REMOTE; + } + } + } catch (Exception e) { + // 记录错误日志 + Log.e(TAG, e.toString()); + // 打印异常堆栈信息 + e.printStackTrace(); + } + + // 发生异常时返回错误操作码 + return SYNC_ACTION_ERROR; +} + +public int getChildTaskCount() { + // 返回子任务列表的大小 + return mChildren.size(); +} + +public boolean addChildTask(Task task) { + // 初始化返回值为false + boolean ret = false; + // 检查任务对象是否为null并且子任务列表中不包含该任务 + if (task != null && !mChildren.contains(task)) { + // 将任务添加到子任务列表中,并更新返回值 + ret = mChildren.add(task); + // 如果添加成功 + if (ret) { + // 需要设置前一个兄弟节点和父节点 + // 如果子任务列表为空,则前一个兄弟节点为null,否则为列表中的最后一个元素 + task.setPriorSibling(mChildren.isEmpty() ? null : mChildren.get(mChildren.size() - 1)); + // 设置当前TaskList为任务的父节点 + task.setParent(this); + } + } + // 返回是否成功添加任务 + return ret; +} + +public boolean addChildTask(Task task, int index) { + // 检查索引是否有效 + if (index < 0 || index > mChildren.size()) { + // 记录错误日志 + Log.e(TAG, "add child task: invalid index"); + // 返回添加失败 + return false; + } + + // 查找任务是否已存在于子任务列表中 + int pos = mChildren.indexOf(task); + // 如果任务不为null且不在子任务列表中 + if (task != null && pos == -1) { + // 在指定索引处添加任务 + mChildren.add(index, task); + + // 更新任务列表 + Task preTask = null; // 前一个任务 + Task afterTask = null; // 后一个任务 + // 如果索引不是第一个,则设置前一个任务 + if (index != 0) + preTask = mChildren.get(index - 1); + // 如果索引不是最后一个,则设置后一个任务 + if (index != mChildren.size() - 1) + afterTask = mChildren.get(index + 1); + + // 设置当前任务的前一个兄弟节点 + task.setPriorSibling(preTask); + // 如果存在后一个任务,则设置其前一个兄弟节点为当前任务 + if (afterTask != null) + afterTask.setPriorSibling(task); + } + + // 返回添加成功 + return true; +} + + + + + + + + + +public boolean removeChildTask(Task task) { + // 初始化返回值为false + boolean ret = false; + // 获取任务在子任务列表中的索引 + int index = mChildren.indexOf(task); + // 如果任务在列表中 + if (index != -1) { + // 从子任务列表中移除任务,并更新返回值 + ret = mChildren.remove(task); + + // 如果移除成功 + if (ret) { + // 重置任务的前一个兄弟节点和父节点 + task.setPriorSibling(null); + task.setParent(null); + + // 更新任务列表 + // 如果移除的任务不是列表中的最后一个 + if (index != mChildren.size()) { + // 设置新的前一个兄弟节点 + // 如果移除的是第一个任务,则新的前一个兄弟节点为null + // 否则为列表中前一个任务 + mChildren.get(index).setPriorSibling( + index == 0 ? null : mChildren.get(index - 1)); + } + } + } + // 返回是否成功移除任务 + return ret; +} + + + + + +// 移动子任务到指定索引位置的方法 + public boolean moveChildTask(Task task, int index) { +// 检查索引是否有效,无效则打印错误日志并返回false + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "move child task: invalid index"); + return false; + } + // 获取任务在列表中的位置 + int pos = mChildren.indexOf(task); + // 如果任务不在列表中,则打印错误日志并返回false + if (pos == -1) { + Log.e(TAG, "move child task: the task should in the list"); + return false; + } + // 如果任务已经在指定位置,则无需移动,直接返回true + if (pos == index) + return true; + // 移除任务后尝试在指定位置添加任务,成功则返回true,失败则返回false + return (removeChildTask(task) && addChildTask(task, index)); + } +// 通过GID查找子任务的方法 + public Task findChildTaskByGid(String gid) { + // 遍历子任务列表,查找GID匹配的任务 + for (int i = 0; i < mChildren.size(); i++) { + Task t = mChildren.get(i); + if (t.getGid().equals(gid)) { + return t; + } + }// 如果没有找到,则返回null + return null; + } +// 获取子任务索引的方法 + public int getChildTaskIndex(Task task) { + // 返回任务在列表中的索引位置 + return mChildren.indexOf(task); + } +// 通过索引获取子任务的方法 + public Task getChildTaskByIndex(int index) { + // 检查索引是否有效,无效则打印错误日志并返回null + if (index < 0 || index >= mChildren.size()) { + Log.e(TAG, "getTaskByIndex: invalid index"); + return null; + } // 返回指定索引位置的子任务 + return mChildren.get(index); + } +// 通过GID获取子任务的方法(存在拼写错误,应为getChildTaskByGid) + public Task getChilTaskByGid(String gid) { + // 遍历子任务列表,查找GID匹配的任务 + for (Task task : mChildren) { + if (task.getGid().equals(gid)) + return task; + } // 如果没有找到,则返回null + return null; + } +// 获取子任务列表的方法 + public ArrayList getChildTaskList() { + return this.mChildren; // 返回子任务列表 + } +// 设置索引的方法 + public void setIndex(int index) { + // 设置当前对象的索引值 + this.mIndex = index; + } +// 获取索引的方法 + public int getIndex() { + return this.mIndex;// 返回当前对象的索引值 + } +}