You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
git-test/src/main/java/net/micode/notes/tool/BackupUtils.java

491 lines
17 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/*
* Copyright (c) 2010-2011, The MiCode Open Source Community (www.micode.net)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.micode.notes.tool;
import android.content.Context;
import android.database.Cursor;
import android.os.Environment;
import android.text.TextUtils;
import android.text.format.DateFormat;
import android.util.Log;
import net.micode.notes.R;
import net.micode.notes.data.Notes;
import net.micode.notes.data.Notes.DataColumns;
import net.micode.notes.data.Notes.DataConstants;
import net.micode.notes.data.Notes.NoteColumns;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
/**
* 备份工具类,负责将笔记导出为文本文件。
* <p>
* 该类采用单例模式实现,提供了将笔记内容导出为用户可读文本格式的功能,
* 支持将不同文件夹的笔记分类导出并处理SD卡状态检查和文件生成等操作。
* </p>
*/
public class BackupUtils {
/**
* 日志标签
*/
private static final String TAG = BackupUtils.class.getSimpleName();
/**
* 单例实例
*/
private static BackupUtils sInstance;
/**
* 获取BackupUtils的单例实例
* <p>
* 如果实例不存在,则创建一个新实例并返回;否则返回已存在的实例。
* </p>
*
* @param context 应用上下文
* @return BackupUtils的单例实例
*/
public static synchronized BackupUtils getInstance(Context context) {
if (sInstance == null) {
sInstance = new BackupUtils(context);
}
return sInstance;
}
/**
* 备份或恢复操作的状态常量
* <p>
* 这些状态常量用于表示备份或恢复操作的执行结果和状态。
* </p>
*/
/**
* SD卡未挂载状态
*/
public static final int STATE_SD_CARD_UNMOUONTED = 0;
/**
* 备份文件不存在状态
*/
public static final int STATE_BACKUP_FILE_NOT_EXIST = 1;
/**
* 数据格式损坏状态,可能被其他程序修改
*/
public static final int STATE_DATA_DESTROIED = 2;
/**
* 运行时异常导致备份或恢复失败状态
*/
public static final int STATE_SYSTEM_ERROR = 3;
/**
* 备份或恢复成功状态
*/
public static final int STATE_SUCCESS = 4;
/**
* 文本导出实例,负责实际的文本导出操作
*/
private TextExport mTextExport;
/**
* 私有构造方法初始化BackupUtils实例
* <p>
* 创建TextExport实例用于文本导出操作。
* </p>
*
* @param context 应用上下文
*/
private BackupUtils(Context context) {
mTextExport = new TextExport(context);
}
/**
* 检查外部存储是否可用
* <p>
* 判断SD卡是否已挂载并可读写。
* </p>
*
* @return 如果外部存储可用返回true否则返回false
*/
private static boolean externalStorageAvailable() {
return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
}
/**
* 将笔记导出为文本文件
* <p>
* 调用内部TextExport实例的exportToText方法执行实际导出操作。
* </p>
*
* @return 导出操作的状态码可能的值为STATE_SD_CARD_UNMOUONTED、STATE_SYSTEM_ERROR或STATE_SUCCESS
*/
public int exportToText() {
return mTextExport.exportToText();
}
/**
* 获取导出的文本文件名
* <p>
* 返回最近一次导出操作生成的文本文件名。
* </p>
*
* @return 导出的文本文件名
*/
public String getExportedTextFileName() {
return mTextExport.mFileName;
}
/**
* 获取导出的文本文件目录
* <p>
* 返回导出文本文件的存储目录路径。
* </p>
*
* @return 导出的文本文件目录路径
*/
public String getExportedTextFileDir() {
return mTextExport.mFileDirectory;
}
/**
* 文本导出内部类,负责实际的笔记文本导出操作。
* <p>
* 该内部类实现了将笔记内容导出为用户可读文本格式的功能,
* 支持按文件夹分类导出,并处理文件生成和内容格式化等操作。
* </p>
*/
private static class TextExport {
private static final String[] NOTE_PROJECTION = {
NoteColumns.ID,
NoteColumns.MODIFIED_DATE,
NoteColumns.SNIPPET,
NoteColumns.TYPE
};
private static final int NOTE_COLUMN_ID = 0;
private static final int NOTE_COLUMN_MODIFIED_DATE = 1;
private static final int NOTE_COLUMN_SNIPPET = 2;
private static final String[] DATA_PROJECTION = {
DataColumns.CONTENT,
DataColumns.MIME_TYPE,
DataColumns.DATA1,
DataColumns.DATA2,
DataColumns.DATA3,
DataColumns.DATA4,
};
private static final int DATA_COLUMN_CONTENT = 0;
private static final int DATA_COLUMN_MIME_TYPE = 1;
private static final int DATA_COLUMN_CALL_DATE = 2;
private static final int DATA_COLUMN_PHONE_NUMBER = 4;
/**
* 文本格式化字符串数组,从资源文件中加载
*/
private final String [] TEXT_FORMAT;
private static final int FORMAT_FOLDER_NAME = 0;
private static final int FORMAT_NOTE_DATE = 1;
private static final int FORMAT_NOTE_CONTENT = 2;
/**
* 应用上下文
*/
private Context mContext;
/**
* 导出的文件名
*/
private String mFileName;
/**
* 导出的文件目录
*/
private String mFileDirectory;
/**
* 构造方法初始化TextExport实例
* <p>
* 从资源文件中加载文本格式化字符串,并初始化上下文和文件信息。
* </p>
*
* @param context 应用上下文
*/
public TextExport(Context context) {
TEXT_FORMAT = context.getResources().getStringArray(R.array.format_for_exported_note);
mContext = context;
mFileName = "";
mFileDirectory = "";
}
/**
* 获取指定ID的文本格式化字符串
* <p>
* 从TEXT_FORMAT数组中获取指定索引的格式化字符串。
* </p>
*
* @param id 格式化字符串的索引
* @return 对应的格式化字符串
*/
private String getFormat(int id) {
return TEXT_FORMAT[id];
}
/**
* 将指定文件夹的笔记导出为文本
* <p>
* 查询并导出指定文件夹下的所有笔记,包括每个笔记的修改日期和内容。
* </p>
*
* @param folderId 文件夹ID
* @param ps 输出流,用于写入导出的文本内容
*/
private void exportFolderToText(String folderId, PrintStream ps) {
// Query notes belong to this folder
Cursor notesCursor = mContext.getContentResolver().query(Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION, NoteColumns.PARENT_ID + "=?", new String[] {
folderId
}, null);
if (notesCursor != null) {
if (notesCursor.moveToFirst()) {
do {
// Print note's last modified date
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
notesCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = notesCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (notesCursor.moveToNext());
}
notesCursor.close();
}
}
/**
* 将指定笔记导出到打印流
* <p>
* 查询并导出指定笔记的详细内容,区分文本笔记和通话笔记的不同格式。
* </p>
*
* @param noteId 笔记ID
* @param ps 打印流,用于写入导出的笔记内容
*/
private void exportNoteToText(String noteId, PrintStream ps) {
Cursor dataCursor = mContext.getContentResolver().query(Notes.CONTENT_DATA_URI,
DATA_PROJECTION, DataColumns.NOTE_ID + "=?", new String[] {
noteId
}, null);
if (dataCursor != null) {
if (dataCursor.moveToFirst()) {
do {
String mimeType = dataCursor.getString(DATA_COLUMN_MIME_TYPE);
if (DataConstants.CALL_NOTE.equals(mimeType)) {
// Print phone number
String phoneNumber = dataCursor.getString(DATA_COLUMN_PHONE_NUMBER);
long callDate = dataCursor.getLong(DATA_COLUMN_CALL_DATE);
String location = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(phoneNumber)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
phoneNumber));
}
// Print call date
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT), DateFormat
.format(mContext.getString(R.string.format_datetime_mdhm),
callDate)));
// Print call attachment location
if (!TextUtils.isEmpty(location)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
location));
}
} else if (DataConstants.NOTE.equals(mimeType)) {
String content = dataCursor.getString(DATA_COLUMN_CONTENT);
if (!TextUtils.isEmpty(content)) {
ps.println(String.format(getFormat(FORMAT_NOTE_CONTENT),
content));
}
}
} while (dataCursor.moveToNext());
}
dataCursor.close();
}
// print a line separator between note
try {
ps.write(new byte[] {
Character.LINE_SEPARATOR, Character.LETTER_NUMBER
});
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
/**
* 将笔记导出为用户可读的文本格式
* <p>
* 检查SD卡状态创建输出文件然后按文件夹分类导出所有笔记内容。
* </p>
*
* @return 导出操作的状态码可能的值为STATE_SD_CARD_UNMOUONTED、STATE_SYSTEM_ERROR或STATE_SUCCESS
*/
public int exportToText() {
if (!externalStorageAvailable()) {
Log.d(TAG, "Media was not mounted");
return STATE_SD_CARD_UNMOUONTED;
}
PrintStream ps = getExportToTextPrintStream();
if (ps == null) {
Log.e(TAG, "get print stream error");
return STATE_SYSTEM_ERROR;
}
// First export folder and its notes
Cursor folderCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
"(" + NoteColumns.TYPE + "=" + Notes.TYPE_FOLDER + " AND "
+ NoteColumns.PARENT_ID + "<>" + Notes.ID_TRASH_FOLER + ") OR "
+ NoteColumns.ID + "=" + Notes.ID_CALL_RECORD_FOLDER, null, null);
if (folderCursor != null) {
if (folderCursor.moveToFirst()) {
do {
// Print folder's name
String folderName = "";
if(folderCursor.getLong(NOTE_COLUMN_ID) == Notes.ID_CALL_RECORD_FOLDER) {
folderName = mContext.getString(R.string.call_record_folder_name);
} else {
folderName = folderCursor.getString(NOTE_COLUMN_SNIPPET);
}
if (!TextUtils.isEmpty(folderName)) {
ps.println(String.format(getFormat(FORMAT_FOLDER_NAME), folderName));
}
String folderId = folderCursor.getString(NOTE_COLUMN_ID);
exportFolderToText(folderId, ps);
} while (folderCursor.moveToNext());
}
folderCursor.close();
}
// Export notes in root's folder
Cursor noteCursor = mContext.getContentResolver().query(
Notes.CONTENT_NOTE_URI,
NOTE_PROJECTION,
NoteColumns.TYPE + "=" + +Notes.TYPE_NOTE + " AND " + NoteColumns.PARENT_ID
+ "=0", null, null);
if (noteCursor != null) {
if (noteCursor.moveToFirst()) {
do {
ps.println(String.format(getFormat(FORMAT_NOTE_DATE), DateFormat.format(
mContext.getString(R.string.format_datetime_mdhm),
noteCursor.getLong(NOTE_COLUMN_MODIFIED_DATE))));
// Query data belong to this note
String noteId = noteCursor.getString(NOTE_COLUMN_ID);
exportNoteToText(noteId, ps);
} while (noteCursor.moveToNext());
}
noteCursor.close();
}
ps.close();
return STATE_SUCCESS;
}
/**
* 获取用于导出文本的打印流
* <p>
* 创建并返回指向导出文本文件的打印流,用于写入导出的笔记内容。
* </p>
*
* @return 指向导出文本文件的打印流如果创建失败返回null
*/
private PrintStream getExportToTextPrintStream() {
File file = generateFileMountedOnSDcard(mContext, R.string.file_path,
R.string.file_name_txt_format);
if (file == null) {
Log.e(TAG, "create file to exported failed");
return null;
}
mFileName = file.getName();
mFileDirectory = mContext.getString(R.string.file_path);
PrintStream ps = null;
try {
FileOutputStream fos = new FileOutputStream(file);
ps = new PrintStream(fos);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
} catch (NullPointerException e) {
e.printStackTrace();
return null;
}
return ps;
}
}
/**
* 在SD卡上生成用于存储导入数据的文本文件
* <p>
* 根据指定的文件路径和文件名格式在SD卡上创建相应的目录和文件。
* </p>
*
* @param context 应用上下文
* @param filePathResId 文件路径资源ID
* @param fileNameFormatResId 文件名格式资源ID
* @return 创建的文件对象如果创建失败返回null
*/
private static File generateFileMountedOnSDcard(Context context, int filePathResId, int fileNameFormatResId) {
StringBuilder sb = new StringBuilder();
sb.append(Environment.getExternalStorageDirectory());
sb.append(context.getString(filePathResId));
File filedir = new File(sb.toString());
sb.append(context.getString(
fileNameFormatResId,
DateFormat.format(context.getString(R.string.format_date_ymd),
System.currentTimeMillis())));
File file = new File(sb.toString());
try {
if (!filedir.exists()) {
filedir.mkdir();
}
if (!file.exists()) {
file.createNewFile();
}
return file;
} catch (SecurityException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}