Anthony Restaino
8 years ago
committed by
GitHub
24 changed files with 1285 additions and 751 deletions
@ -0,0 +1,48 @@ |
|||||||
|
package acr.browser.lightning.activity; |
||||||
|
|
||||||
|
import android.support.annotation.Nullable; |
||||||
|
|
||||||
|
import acr.browser.lightning.browser.BookmarksView; |
||||||
|
|
||||||
|
/** |
||||||
|
* The UI model representing the current folder shown |
||||||
|
* by the {@link BookmarksView}. |
||||||
|
* <p> |
||||||
|
* Created by anthonycr on 5/7/17. |
||||||
|
*/ |
||||||
|
public class BookmarkUiModel { |
||||||
|
|
||||||
|
@Nullable private String mCurrentFolder; |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets the current folder that is being shown. |
||||||
|
* Use null as the root folder. |
||||||
|
* |
||||||
|
* @param folder the current folder, null for root. |
||||||
|
*/ |
||||||
|
public void setCurrentFolder(@Nullable String folder) { |
||||||
|
mCurrentFolder = folder; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines if the current folder is |
||||||
|
* the root folder. |
||||||
|
* |
||||||
|
* @return true if the current folder is |
||||||
|
* the root, false otherwise. |
||||||
|
*/ |
||||||
|
public boolean isRootFolder() { |
||||||
|
return mCurrentFolder == null; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the current folder that is being shown. |
||||||
|
* |
||||||
|
* @return the current folder, null for root. |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public String getCurrentFolder() { |
||||||
|
return mCurrentFolder; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -1,552 +0,0 @@ |
|||||||
package acr.browser.lightning.database; |
|
||||||
|
|
||||||
import android.app.Activity; |
|
||||||
import android.content.Context; |
|
||||||
import android.os.Environment; |
|
||||||
import android.support.annotation.NonNull; |
|
||||||
import android.support.annotation.Nullable; |
|
||||||
import android.util.Log; |
|
||||||
|
|
||||||
import org.json.JSONException; |
|
||||||
import org.json.JSONObject; |
|
||||||
|
|
||||||
import java.io.BufferedReader; |
|
||||||
import java.io.BufferedWriter; |
|
||||||
import java.io.File; |
|
||||||
import java.io.FileInputStream; |
|
||||||
import java.io.FileReader; |
|
||||||
import java.io.FileWriter; |
|
||||||
import java.io.IOException; |
|
||||||
import java.io.InputStream; |
|
||||||
import java.io.InputStreamReader; |
|
||||||
import java.util.ArrayList; |
|
||||||
import java.util.Collections; |
|
||||||
import java.util.Comparator; |
|
||||||
import java.util.HashMap; |
|
||||||
import java.util.HashSet; |
|
||||||
import java.util.LinkedList; |
|
||||||
import java.util.List; |
|
||||||
import java.util.Locale; |
|
||||||
import java.util.Map; |
|
||||||
import java.util.Set; |
|
||||||
import java.util.concurrent.Executor; |
|
||||||
|
|
||||||
import javax.inject.Inject; |
|
||||||
import javax.inject.Singleton; |
|
||||||
|
|
||||||
import acr.browser.lightning.R; |
|
||||||
import acr.browser.lightning.app.BrowserApp; |
|
||||||
import acr.browser.lightning.constant.Constants; |
|
||||||
import acr.browser.lightning.utils.Utils; |
|
||||||
|
|
||||||
@Singleton |
|
||||||
public class BookmarkManager { |
|
||||||
|
|
||||||
private static final String TAG = "BookmarkManager"; |
|
||||||
|
|
||||||
private static final String TITLE = "title"; |
|
||||||
private static final String URL = "url"; |
|
||||||
private static final String FOLDER = "folder"; |
|
||||||
private static final String ORDER = "order"; |
|
||||||
private static final String FILE_BOOKMARKS = "bookmarks.dat"; |
|
||||||
|
|
||||||
@NonNull private final String DEFAULT_BOOKMARK_TITLE; |
|
||||||
|
|
||||||
private Map<String, HistoryItem> mBookmarksMap; |
|
||||||
@NonNull private String mCurrentFolder = ""; |
|
||||||
@NonNull private final Executor mExecutor; |
|
||||||
private File mFilesDir; |
|
||||||
|
|
||||||
@Inject |
|
||||||
public BookmarkManager(@NonNull Context context) { |
|
||||||
mExecutor = BrowserApp.getIOThread(); |
|
||||||
DEFAULT_BOOKMARK_TITLE = context.getString(R.string.untitled); |
|
||||||
mExecutor.execute(new BookmarkInitializer(context)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Initialize the BookmarkManager, it's a one-time operation and will be executed asynchronously. |
|
||||||
* When done, mReady flag will been set to true. |
|
||||||
*/ |
|
||||||
private class BookmarkInitializer implements Runnable { |
|
||||||
private final Context mContext; |
|
||||||
|
|
||||||
BookmarkInitializer(Context context) { |
|
||||||
mContext = context; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
synchronized (BookmarkManager.this) { |
|
||||||
mFilesDir = mContext.getFilesDir(); |
|
||||||
final Map<String, HistoryItem> bookmarks = new HashMap<>(); |
|
||||||
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS); |
|
||||||
|
|
||||||
BufferedReader bookmarksReader = null; |
|
||||||
InputStream inputStream = null; |
|
||||||
try { |
|
||||||
if (bookmarksFile.exists() && bookmarksFile.isFile()) { |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
inputStream = new FileInputStream(bookmarksFile); |
|
||||||
} else { |
|
||||||
inputStream = mContext.getResources().openRawResource(R.raw.default_bookmarks); |
|
||||||
} |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
bookmarksReader = new BufferedReader(new InputStreamReader(inputStream)); |
|
||||||
String line; |
|
||||||
while ((line = bookmarksReader.readLine()) != null) { |
|
||||||
try { |
|
||||||
JSONObject object = new JSONObject(line); |
|
||||||
HistoryItem item = new HistoryItem(); |
|
||||||
item.setTitle(object.getString(TITLE)); |
|
||||||
final String url = object.getString(URL); |
|
||||||
item.setUrl(url); |
|
||||||
item.setFolder(object.getString(FOLDER)); |
|
||||||
item.setOrder(object.getInt(ORDER)); |
|
||||||
item.setImageId(R.drawable.ic_bookmark); |
|
||||||
bookmarks.put(url, item); |
|
||||||
} catch (JSONException e) { |
|
||||||
Log.e(TAG, "Can't parse line " + line, e); |
|
||||||
} |
|
||||||
} |
|
||||||
} catch (IOException e) { |
|
||||||
Log.e(TAG, "Error reading the bookmarks file", e); |
|
||||||
} finally { |
|
||||||
Utils.close(bookmarksReader); |
|
||||||
Utils.close(inputStream); |
|
||||||
} |
|
||||||
mBookmarksMap = bookmarks; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Dump all the given bookmarks to the bookmark file using a temporary file |
|
||||||
*/ |
|
||||||
private class BookmarksWriter implements Runnable { |
|
||||||
|
|
||||||
private final List<HistoryItem> mBookmarks; |
|
||||||
|
|
||||||
BookmarksWriter(List<HistoryItem> bookmarks) { |
|
||||||
mBookmarks = bookmarks; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
public void run() { |
|
||||||
final File tempFile = new File(mFilesDir, |
|
||||||
String.format(Locale.US, "bm_%d.dat", System.currentTimeMillis())); |
|
||||||
final File bookmarksFile = new File(mFilesDir, FILE_BOOKMARKS); |
|
||||||
boolean success = false; |
|
||||||
BufferedWriter bookmarkWriter = null; |
|
||||||
try { |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
bookmarkWriter = new BufferedWriter(new FileWriter(tempFile, false)); |
|
||||||
JSONObject object = new JSONObject(); |
|
||||||
for (HistoryItem item : mBookmarks) { |
|
||||||
object.put(TITLE, item.getTitle()); |
|
||||||
object.put(URL, item.getUrl()); |
|
||||||
object.put(FOLDER, item.getFolder()); |
|
||||||
object.put(ORDER, item.getOrder()); |
|
||||||
bookmarkWriter.write(object.toString()); |
|
||||||
bookmarkWriter.newLine(); |
|
||||||
} |
|
||||||
success = true; |
|
||||||
} catch (@NonNull IOException | JSONException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
Utils.close(bookmarkWriter); |
|
||||||
} |
|
||||||
|
|
||||||
if (success) { |
|
||||||
// Overwrite the bookmarks file by renaming the temp file
|
|
||||||
//noinspection ResultOfMethodCallIgnored
|
|
||||||
tempFile.renameTo(bookmarksFile); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Look for bookmark using the url |
|
||||||
* |
|
||||||
* @param url the lookup url |
|
||||||
* @return the bookmark as an {@link HistoryItem} or null |
|
||||||
*/ |
|
||||||
@Nullable |
|
||||||
public HistoryItem findBookmarkForUrl(final String url) { |
|
||||||
return mBookmarksMap.get(url); |
|
||||||
} |
|
||||||
|
|
||||||
public boolean isBookmark(String url) { |
|
||||||
return mBookmarksMap.containsKey(url); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method adds the the HistoryItem item to permanent bookmark storage.<br> |
|
||||||
* This operation is blocking if the manager is still not ready. |
|
||||||
* |
|
||||||
* @param item the item to add |
|
||||||
* @return It returns true if the operation was successful. |
|
||||||
*/ |
|
||||||
public synchronized boolean addBookmark(@NonNull HistoryItem item) { |
|
||||||
final String url = item.getUrl(); |
|
||||||
if (mBookmarksMap.containsKey(url)) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
mBookmarksMap.put(url, item); |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method adds the list of HistoryItems to permanent bookmark storage |
|
||||||
* |
|
||||||
* @param list the list of HistoryItems to add to bookmarks |
|
||||||
*/ |
|
||||||
public synchronized void addBookmarkList(@Nullable List<HistoryItem> list) { |
|
||||||
if (list == null || list.isEmpty()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for (HistoryItem item : list) { |
|
||||||
final String url = item.getUrl(); |
|
||||||
if (!mBookmarksMap.containsKey(url)) { |
|
||||||
mBookmarksMap.put(url, item); |
|
||||||
} |
|
||||||
} |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method deletes the bookmark with the given url. It returns |
|
||||||
* true if the deletion was successful. |
|
||||||
* |
|
||||||
* @param deleteItem the bookmark item to delete |
|
||||||
*/ |
|
||||||
public synchronized boolean deleteBookmark(@Nullable HistoryItem deleteItem) { |
|
||||||
if (deleteItem == null || deleteItem.isFolder()) { |
|
||||||
return false; |
|
||||||
} |
|
||||||
mBookmarksMap.remove(deleteItem.getUrl()); |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
return true; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* renames a folder and moves all it's contents to that folder |
|
||||||
* |
|
||||||
* @param oldName the folder to be renamed |
|
||||||
* @param newName the new name of the folder |
|
||||||
*/ |
|
||||||
public synchronized void renameFolder(@NonNull String oldName, @NonNull String newName) { |
|
||||||
if (newName.isEmpty()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
if (item.getFolder().equals(oldName)) { |
|
||||||
item.setFolder(newName); |
|
||||||
} else if (item.isFolder() && item.getTitle().equals(oldName)) { |
|
||||||
item.setTitle(newName); |
|
||||||
item.setUrl(Constants.FOLDER + newName); |
|
||||||
} |
|
||||||
} |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Delete the folder and move all bookmarks to the top level |
|
||||||
* |
|
||||||
* @param name the name of the folder to be deleted |
|
||||||
*/ |
|
||||||
public synchronized void deleteFolder(@NonNull String name) { |
|
||||||
final Map<String, HistoryItem> bookmarks = new HashMap<>(); |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
final String url = item.getUrl(); |
|
||||||
if (item.isFolder()) { |
|
||||||
if (!item.getTitle().equals(name)) { |
|
||||||
bookmarks.put(url, item); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (item.getFolder().equals(name)) { |
|
||||||
item.setFolder(""); |
|
||||||
} |
|
||||||
bookmarks.put(url, item); |
|
||||||
} |
|
||||||
} |
|
||||||
mBookmarksMap = bookmarks; |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method deletes ALL bookmarks created |
|
||||||
* by the user. Use this method carefully and |
|
||||||
* do not use it without explicit user consent. |
|
||||||
*/ |
|
||||||
public synchronized void deleteAllBookmarks() { |
|
||||||
mBookmarksMap = new HashMap<>(); |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method edits a particular bookmark in the bookmark database |
|
||||||
* |
|
||||||
* @param oldItem This is the old item that you wish to edit |
|
||||||
* @param newItem This is the new item that will overwrite the old item |
|
||||||
*/ |
|
||||||
public synchronized void editBookmark(@Nullable HistoryItem oldItem, @Nullable HistoryItem newItem) { |
|
||||||
if (oldItem == null || newItem == null || oldItem.isFolder()) { |
|
||||||
return; |
|
||||||
} |
|
||||||
if (newItem.getUrl().isEmpty()) { |
|
||||||
deleteBookmark(oldItem); |
|
||||||
return; |
|
||||||
} |
|
||||||
if (newItem.getTitle().isEmpty()) { |
|
||||||
newItem.setTitle(DEFAULT_BOOKMARK_TITLE); |
|
||||||
} |
|
||||||
final String oldUrl = oldItem.getUrl(); |
|
||||||
final String newUrl = newItem.getUrl(); |
|
||||||
if (!oldUrl.equals(newUrl)) { |
|
||||||
// The url has been changed, remove the old one
|
|
||||||
mBookmarksMap.remove(oldUrl); |
|
||||||
} |
|
||||||
mBookmarksMap.put(newUrl, newItem); |
|
||||||
mExecutor.execute(new BookmarksWriter(new LinkedList<>(mBookmarksMap.values()))); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method exports the stored bookmarks to a text file in the device's |
|
||||||
* external download directory |
|
||||||
*/ |
|
||||||
public synchronized void exportBookmarks(@NonNull Activity activity) { |
|
||||||
List<HistoryItem> bookmarkList = getAllBookmarks(true); |
|
||||||
File bookmarksExport = new File( |
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), |
|
||||||
"BookmarksExport.txt"); |
|
||||||
int counter = 0; |
|
||||||
while (bookmarksExport.exists()) { |
|
||||||
counter++; |
|
||||||
bookmarksExport = new File( |
|
||||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), |
|
||||||
"BookmarksExport-" + counter + ".txt"); |
|
||||||
} |
|
||||||
BufferedWriter bookmarkWriter = null; |
|
||||||
try { |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
bookmarkWriter = new BufferedWriter(new FileWriter(bookmarksExport, |
|
||||||
false)); |
|
||||||
JSONObject object = new JSONObject(); |
|
||||||
for (HistoryItem item : bookmarkList) { |
|
||||||
object.put(TITLE, item.getTitle()); |
|
||||||
object.put(URL, item.getUrl()); |
|
||||||
object.put(FOLDER, item.getFolder()); |
|
||||||
object.put(ORDER, item.getOrder()); |
|
||||||
bookmarkWriter.write(object.toString()); |
|
||||||
bookmarkWriter.newLine(); |
|
||||||
} |
|
||||||
Utils.showSnackbar(activity, activity.getString(R.string.bookmark_export_path) |
|
||||||
+ ' ' + bookmarksExport.getPath()); |
|
||||||
} catch (@NonNull IOException | JSONException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
} finally { |
|
||||||
Utils.close(bookmarkWriter); |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns a list of ALL stored bookmarks. |
|
||||||
* This is a disk-bound operation and should not be |
|
||||||
* done very frequently. |
|
||||||
* |
|
||||||
* @param sort force to sort the returned bookmarkList |
|
||||||
* @return returns a list of bookmarks that can be sorted |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public synchronized List<HistoryItem> getAllBookmarks(boolean sort) { |
|
||||||
final List<HistoryItem> bookmarks = new ArrayList<>(mBookmarksMap.values()); |
|
||||||
if (sort) { |
|
||||||
Collections.sort(bookmarks, new SortIgnoreCase()); |
|
||||||
} |
|
||||||
return bookmarks; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns a list of bookmarks and folders located in the specified folder. |
|
||||||
* This method should generally be used by the UI when it needs a list to display to the |
|
||||||
* user as it returns a subset of all bookmarks and includes folders as well which are |
|
||||||
* really 'fake' bookmarks. |
|
||||||
* |
|
||||||
* @param folder the name of the folder to retrieve bookmarks from |
|
||||||
* @return a list of bookmarks found in that folder |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public synchronized List<HistoryItem> getBookmarksFromFolder(@Nullable String folder, boolean sort) { |
|
||||||
List<HistoryItem> bookmarks = new ArrayList<>(1); |
|
||||||
if (folder == null || folder.isEmpty()) { |
|
||||||
bookmarks.addAll(getFolders(sort)); |
|
||||||
folder = ""; |
|
||||||
} |
|
||||||
mCurrentFolder = folder; |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
if (item.getFolder().equals(folder)) |
|
||||||
bookmarks.add(item); |
|
||||||
} |
|
||||||
if (sort) { |
|
||||||
Collections.sort(bookmarks, new SortIgnoreCase()); |
|
||||||
} |
|
||||||
return bookmarks; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Different from {@link #getBookmarksFromFolder(String, boolean)} only in |
|
||||||
* that it doesn't affect the internal state of the bookmark manager which |
|
||||||
* tracks the current folder used by the bookmark drawer. |
|
||||||
* <p/> |
|
||||||
* This method returns a list of bookmarks and folders located in the specified folder. |
|
||||||
* This method should generally be used by the UI when it needs a list to display to the |
|
||||||
* user as it returns a subset of all bookmarks and includes folders as well which are |
|
||||||
* really 'fake' bookmarks. |
|
||||||
* |
|
||||||
* @param folder the name of the folder to retrieve bookmarks from |
|
||||||
* @return a list of bookmarks found in that folder |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public synchronized List<HistoryItem> getBookmarksCopyFromFolder(@Nullable String folder, boolean sort) { |
|
||||||
List<HistoryItem> bookmarks = new ArrayList<>(1); |
|
||||||
if (folder == null || folder.isEmpty()) { |
|
||||||
bookmarks.addAll(getFolders(sort)); |
|
||||||
folder = ""; |
|
||||||
} |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
if (item.getFolder().equals(folder)) |
|
||||||
bookmarks.add(item); |
|
||||||
} |
|
||||||
if (sort) { |
|
||||||
Collections.sort(bookmarks, new SortIgnoreCase()); |
|
||||||
} |
|
||||||
return bookmarks; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Tells you if you are at the root folder or in a subfolder |
|
||||||
* |
|
||||||
* @return returns true if you are in the root folder |
|
||||||
*/ |
|
||||||
public boolean isRootFolder() { |
|
||||||
return mCurrentFolder.isEmpty(); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the current folder |
|
||||||
* |
|
||||||
* @return the current folder |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public String getCurrentFolder() { |
|
||||||
return mCurrentFolder; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method returns a list of all folders. |
|
||||||
* Folders cannot be empty as they are generated from |
|
||||||
* the list of bookmarks that have non-empty folder fields. |
|
||||||
* |
|
||||||
* @return a list of all folders |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
private synchronized List<HistoryItem> getFolders(boolean sort) { |
|
||||||
final HashMap<String, HistoryItem> folders = new HashMap<>(); |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
final String folderName = item.getFolder(); |
|
||||||
if (!folderName.isEmpty() && !folders.containsKey(folderName)) { |
|
||||||
final HistoryItem folder = new HistoryItem(); |
|
||||||
folder.setIsFolder(true); |
|
||||||
folder.setTitle(folderName); |
|
||||||
folder.setImageId(R.drawable.ic_folder); |
|
||||||
folder.setUrl(Constants.FOLDER + folderName); |
|
||||||
folders.put(folderName, folder); |
|
||||||
} |
|
||||||
} |
|
||||||
final List<HistoryItem> result = new ArrayList<>(folders.values()); |
|
||||||
if (sort) { |
|
||||||
Collections.sort(result, new SortIgnoreCase()); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* returns a list of folder titles that can be used for suggestions in a |
|
||||||
* simple list adapter |
|
||||||
* |
|
||||||
* @return a list of folder title strings |
|
||||||
*/ |
|
||||||
@NonNull |
|
||||||
public synchronized List<String> getFolderTitles() { |
|
||||||
final Set<String> folders = new HashSet<>(); |
|
||||||
for (HistoryItem item : mBookmarksMap.values()) { |
|
||||||
final String folderName = item.getFolder(); |
|
||||||
if (!folderName.isEmpty()) { |
|
||||||
folders.add(folderName); |
|
||||||
} |
|
||||||
} |
|
||||||
return new ArrayList<>(folders); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This method imports the bookmarks from a backup file that is located on |
|
||||||
* external storage |
|
||||||
* |
|
||||||
* @param file the file to attempt to import bookmarks from |
|
||||||
*/ |
|
||||||
public synchronized void importBookmarksFromFile(@Nullable File file, @NonNull Activity activity) { |
|
||||||
if (file == null) { |
|
||||||
return; |
|
||||||
} |
|
||||||
List<HistoryItem> list = new ArrayList<>(); |
|
||||||
BufferedReader bookmarksReader = null; |
|
||||||
try { |
|
||||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
|
||||||
bookmarksReader = new BufferedReader(new FileReader(file)); |
|
||||||
String line; |
|
||||||
int number = 0; |
|
||||||
while ((line = bookmarksReader.readLine()) != null) { |
|
||||||
JSONObject object = new JSONObject(line); |
|
||||||
HistoryItem item = new HistoryItem(); |
|
||||||
item.setTitle(object.getString(TITLE)); |
|
||||||
item.setUrl(object.getString(URL)); |
|
||||||
item.setFolder(object.getString(FOLDER)); |
|
||||||
item.setOrder(object.getInt(ORDER)); |
|
||||||
list.add(item); |
|
||||||
number++; |
|
||||||
} |
|
||||||
addBookmarkList(list); |
|
||||||
String message = activity.getResources().getString(R.string.message_import); |
|
||||||
Utils.showSnackbar(activity, number + " " + message); |
|
||||||
} catch (@NonNull IOException | JSONException e) { |
|
||||||
e.printStackTrace(); |
|
||||||
Utils.createInformativeDialog(activity, R.string.title_error, R.string.import_bookmark_error); |
|
||||||
} finally { |
|
||||||
Utils.close(bookmarksReader); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* This class sorts bookmarks alphabetically, with folders coming after bookmarks |
|
||||||
*/ |
|
||||||
private static class SortIgnoreCase implements Comparator<HistoryItem> { |
|
||||||
|
|
||||||
public int compare(@Nullable HistoryItem o1, @Nullable HistoryItem o2) { |
|
||||||
if (o1 == null || o2 == null) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
if (o1.isFolder() == o2.isFolder()) { |
|
||||||
return o1.getTitle().toLowerCase(Locale.getDefault()) |
|
||||||
.compareTo(o2.getTitle().toLowerCase(Locale.getDefault())); |
|
||||||
|
|
||||||
} else { |
|
||||||
return o1.isFolder() ? 1 : -1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
} |
|
@ -0,0 +1,390 @@ |
|||||||
|
package acr.browser.lightning.database.bookmark; |
||||||
|
|
||||||
|
import android.app.Application; |
||||||
|
import android.content.ContentValues; |
||||||
|
import android.database.Cursor; |
||||||
|
import android.database.DatabaseUtils; |
||||||
|
import android.database.sqlite.SQLiteDatabase; |
||||||
|
import android.database.sqlite.SQLiteOpenHelper; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
import android.text.TextUtils; |
||||||
|
|
||||||
|
import com.anthonycr.bonsai.Completable; |
||||||
|
import com.anthonycr.bonsai.CompletableAction; |
||||||
|
import com.anthonycr.bonsai.CompletableSubscriber; |
||||||
|
import com.anthonycr.bonsai.Single; |
||||||
|
import com.anthonycr.bonsai.SingleAction; |
||||||
|
import com.anthonycr.bonsai.SingleSubscriber; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.inject.Inject; |
||||||
|
import javax.inject.Singleton; |
||||||
|
|
||||||
|
import acr.browser.lightning.R; |
||||||
|
import acr.browser.lightning.constant.Constants; |
||||||
|
import acr.browser.lightning.database.HistoryItem; |
||||||
|
|
||||||
|
/** |
||||||
|
* The disk backed bookmark database. |
||||||
|
* See {@link BookmarkModel} for method |
||||||
|
* documentation. |
||||||
|
* <p> |
||||||
|
* Created by anthonycr on 5/6/17. |
||||||
|
*/ |
||||||
|
@Singleton |
||||||
|
public class BookmarkDatabase extends SQLiteOpenHelper implements BookmarkModel { |
||||||
|
|
||||||
|
private static final String TAG = "BookmarkDatabase"; |
||||||
|
|
||||||
|
// Database Version
|
||||||
|
private static final int DATABASE_VERSION = 1; |
||||||
|
|
||||||
|
// Database Name
|
||||||
|
private static final String DATABASE_NAME = "bookmarkManager"; |
||||||
|
|
||||||
|
// HistoryItems table name
|
||||||
|
private static final String TABLE_BOOKMARK = "bookmark"; |
||||||
|
|
||||||
|
// HistoryItems Table Columns names
|
||||||
|
private static final String KEY_ID = "id"; |
||||||
|
private static final String KEY_URL = "url"; |
||||||
|
private static final String KEY_TITLE = "title"; |
||||||
|
private static final String KEY_FOLDER = "folder"; |
||||||
|
private static final String KEY_POSITION = "position"; |
||||||
|
|
||||||
|
|
||||||
|
@NonNull private final String DEFAULT_BOOKMARK_TITLE; |
||||||
|
|
||||||
|
@Nullable private SQLiteDatabase mDatabase; |
||||||
|
|
||||||
|
@Inject |
||||||
|
public BookmarkDatabase(@NonNull Application application) { |
||||||
|
super(application, DATABASE_NAME, null, DATABASE_VERSION); |
||||||
|
DEFAULT_BOOKMARK_TITLE = application.getString(R.string.untitled); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Lazily initializes the database |
||||||
|
* field when called. |
||||||
|
* |
||||||
|
* @return a non null writable database. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
private SQLiteDatabase lazyDatabase() { |
||||||
|
if (mDatabase == null) { |
||||||
|
mDatabase = getWritableDatabase(); |
||||||
|
} |
||||||
|
|
||||||
|
return mDatabase; |
||||||
|
} |
||||||
|
|
||||||
|
// Creating Tables
|
||||||
|
@Override |
||||||
|
public void onCreate(@NonNull SQLiteDatabase db) { |
||||||
|
String CREATE_BOOKMARK_TABLE = "CREATE TABLE " + |
||||||
|
DatabaseUtils.sqlEscapeString(TABLE_BOOKMARK) + '(' + |
||||||
|
DatabaseUtils.sqlEscapeString(KEY_ID) + " INTEGER PRIMARY KEY," + |
||||||
|
DatabaseUtils.sqlEscapeString(KEY_URL) + " TEXT," + |
||||||
|
DatabaseUtils.sqlEscapeString(KEY_TITLE) + " TEXT," + |
||||||
|
DatabaseUtils.sqlEscapeString(KEY_FOLDER) + " TEXT," + |
||||||
|
DatabaseUtils.sqlEscapeString(KEY_POSITION) + " INTEGER" + ')'; |
||||||
|
db.execSQL(CREATE_BOOKMARK_TABLE); |
||||||
|
} |
||||||
|
|
||||||
|
// Upgrading database
|
||||||
|
@Override |
||||||
|
public void onUpgrade(@NonNull SQLiteDatabase db, int oldVersion, int newVersion) { |
||||||
|
// Drop older table if it exists
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS " + DatabaseUtils.sqlEscapeString(TABLE_BOOKMARK)); |
||||||
|
// Create tables again
|
||||||
|
onCreate(db); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private static ContentValues bindBookmarkToContentValues(@NonNull HistoryItem bookmarkItem) { |
||||||
|
ContentValues contentValues = new ContentValues(4); |
||||||
|
contentValues.put(KEY_TITLE, bookmarkItem.getTitle()); |
||||||
|
contentValues.put(KEY_URL, bookmarkItem.getUrl()); |
||||||
|
contentValues.put(KEY_FOLDER, bookmarkItem.getFolder()); |
||||||
|
contentValues.put(KEY_POSITION, bookmarkItem.getPosition()); |
||||||
|
|
||||||
|
return contentValues; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private static HistoryItem bindCursorToHistoryItem(@NonNull Cursor cursor) { |
||||||
|
HistoryItem bookmark = new HistoryItem(); |
||||||
|
|
||||||
|
bookmark.setImageId(R.drawable.ic_bookmark); |
||||||
|
bookmark.setUrl(cursor.getString(cursor.getColumnIndex(KEY_URL))); |
||||||
|
bookmark.setTitle(cursor.getString(cursor.getColumnIndex(KEY_TITLE))); |
||||||
|
bookmark.setFolder(cursor.getString(cursor.getColumnIndex(KEY_FOLDER))); |
||||||
|
bookmark.setPosition(cursor.getInt(cursor.getColumnIndex(KEY_POSITION))); |
||||||
|
|
||||||
|
return bookmark; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private static List<HistoryItem> bindCursorToHistoryItemList(@NonNull Cursor cursor) { |
||||||
|
List<HistoryItem> bookmarks = new ArrayList<>(); |
||||||
|
|
||||||
|
while (cursor.moveToNext()) { |
||||||
|
bookmarks.add(bindCursorToHistoryItem(cursor)); |
||||||
|
} |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
|
||||||
|
return bookmarks; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<HistoryItem> findBookmarkForUrl(@NonNull final String url) { |
||||||
|
return Single.create(new SingleAction<HistoryItem>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<HistoryItem> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); |
||||||
|
|
||||||
|
if (cursor.moveToFirst()) { |
||||||
|
subscriber.onItem(bindCursorToHistoryItem(cursor)); |
||||||
|
} else { |
||||||
|
subscriber.onItem(null); |
||||||
|
} |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<Boolean> isBookmark(@NonNull final String url) { |
||||||
|
return Single.create(new SingleAction<Boolean>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{url}, null, null, null, "1"); |
||||||
|
|
||||||
|
subscriber.onItem(cursor.moveToFirst()); |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<Boolean> addBookmarkIfNotExists(@NonNull final HistoryItem item) { |
||||||
|
return Single.create(new SingleAction<Boolean>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_URL + "=?", new String[]{item.getUrl()}, null, null, null, "1"); |
||||||
|
|
||||||
|
if (cursor.moveToFirst()) { |
||||||
|
cursor.close(); |
||||||
|
subscriber.onItem(false); |
||||||
|
subscriber.onComplete(); |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
|
||||||
|
long id = lazyDatabase().insert(TABLE_BOOKMARK, null, bindBookmarkToContentValues(item)); |
||||||
|
|
||||||
|
subscriber.onItem(id != -1); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Completable addBookmarkList(@NonNull final List<HistoryItem> bookmarkItems) { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||||
|
lazyDatabase().beginTransaction(); |
||||||
|
|
||||||
|
for (HistoryItem item : bookmarkItems) { |
||||||
|
addBookmarkIfNotExists(item).subscribe(); |
||||||
|
} |
||||||
|
|
||||||
|
lazyDatabase().setTransactionSuccessful(); |
||||||
|
lazyDatabase().endTransaction(); |
||||||
|
|
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<Boolean> deleteBookmark(@NonNull final HistoryItem bookmark) { |
||||||
|
return Single.create(new SingleAction<Boolean>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<Boolean> subscriber) { |
||||||
|
int rows = lazyDatabase().delete(TABLE_BOOKMARK, KEY_URL + "=?", new String[]{bookmark.getUrl()}); |
||||||
|
|
||||||
|
subscriber.onItem(rows > 0); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Completable renameFolder(@NonNull final String oldName, @NonNull final String newName) { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||||
|
ContentValues contentValues = new ContentValues(1); |
||||||
|
contentValues.put(KEY_FOLDER, newName); |
||||||
|
|
||||||
|
lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_FOLDER + "=?", new String[]{oldName}); |
||||||
|
|
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Completable deleteFolder(@NonNull final String folderToDelete) { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||||
|
renameFolder(folderToDelete, "").subscribe(); |
||||||
|
|
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Completable deleteAllBookmarks() { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||||
|
lazyDatabase().delete(TABLE_BOOKMARK, null, null); |
||||||
|
|
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Completable editBookmark(@NonNull final HistoryItem oldBookmark, @NonNull final HistoryItem newBookmark) { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull CompletableSubscriber subscriber) { |
||||||
|
if (newBookmark.getTitle().isEmpty()) { |
||||||
|
newBookmark.setTitle(DEFAULT_BOOKMARK_TITLE); |
||||||
|
} |
||||||
|
ContentValues contentValues = bindBookmarkToContentValues(newBookmark); |
||||||
|
|
||||||
|
lazyDatabase().update(TABLE_BOOKMARK, contentValues, KEY_URL + "=?", new String[]{oldBookmark.getUrl()}); |
||||||
|
|
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<List<HistoryItem>> getAllBookmarks() { |
||||||
|
return Single.create(new SingleAction<List<HistoryItem>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, null, null, null, null, null); |
||||||
|
|
||||||
|
subscriber.onItem(bindCursorToHistoryItemList(cursor)); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<List<HistoryItem>> getBookmarksFromFolder(@Nullable final String folder) { |
||||||
|
return Single.create(new SingleAction<List<HistoryItem>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
||||||
|
String finalFolder = folder != null ? folder : ""; |
||||||
|
Cursor cursor = lazyDatabase().query(TABLE_BOOKMARK, null, KEY_FOLDER + "=?", new String[]{finalFolder}, null, null, null); |
||||||
|
|
||||||
|
subscriber.onItem(bindCursorToHistoryItemList(cursor)); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<List<HistoryItem>> getFolders() { |
||||||
|
return Single.create(new SingleAction<List<HistoryItem>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null); |
||||||
|
|
||||||
|
List<HistoryItem> folders = new ArrayList<>(); |
||||||
|
while (cursor.moveToNext()) { |
||||||
|
String folderName = cursor.getString(cursor.getColumnIndex(KEY_FOLDER)); |
||||||
|
if (TextUtils.isEmpty(folderName)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
final HistoryItem folder = new HistoryItem(); |
||||||
|
folder.setIsFolder(true); |
||||||
|
folder.setTitle(folderName); |
||||||
|
folder.setImageId(R.drawable.ic_folder); |
||||||
|
folder.setUrl(Constants.FOLDER + folderName); |
||||||
|
|
||||||
|
folders.add(folder); |
||||||
|
} |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
|
||||||
|
subscriber.onItem(folders); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
@Override |
||||||
|
public Single<List<String>> getFolderNames() { |
||||||
|
return Single.create(new SingleAction<List<String>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<List<String>> subscriber) { |
||||||
|
Cursor cursor = lazyDatabase().query(true, TABLE_BOOKMARK, new String[]{KEY_FOLDER}, null, null, null, null, null, null); |
||||||
|
|
||||||
|
List<String> folders = new ArrayList<>(); |
||||||
|
while (cursor.moveToNext()) { |
||||||
|
String folderName = cursor.getString(cursor.getColumnIndex(KEY_FOLDER)); |
||||||
|
if (TextUtils.isEmpty(folderName)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
folders.add(folderName); |
||||||
|
} |
||||||
|
|
||||||
|
cursor.close(); |
||||||
|
|
||||||
|
subscriber.onItem(folders); |
||||||
|
subscriber.onComplete(); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public long count() { |
||||||
|
return DatabaseUtils.queryNumEntries(lazyDatabase(), TABLE_BOOKMARK); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,201 @@ |
|||||||
|
package acr.browser.lightning.database.bookmark; |
||||||
|
|
||||||
|
import android.content.Context; |
||||||
|
import android.os.Environment; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.WorkerThread; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import com.anthonycr.bonsai.Completable; |
||||||
|
import com.anthonycr.bonsai.CompletableAction; |
||||||
|
import com.anthonycr.bonsai.CompletableSubscriber; |
||||||
|
import com.anthonycr.bonsai.Single; |
||||||
|
import com.anthonycr.bonsai.SingleAction; |
||||||
|
import com.anthonycr.bonsai.SingleSubscriber; |
||||||
|
|
||||||
|
import org.json.JSONException; |
||||||
|
import org.json.JSONObject; |
||||||
|
|
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.BufferedWriter; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileReader; |
||||||
|
import java.io.FileWriter; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import acr.browser.lightning.R; |
||||||
|
import acr.browser.lightning.database.HistoryItem; |
||||||
|
import acr.browser.lightning.utils.Preconditions; |
||||||
|
import acr.browser.lightning.utils.Utils; |
||||||
|
|
||||||
|
/** |
||||||
|
* The class responsible for importing and exporting |
||||||
|
* bookmarks in the JSON format. |
||||||
|
* <p> |
||||||
|
* Created by anthonycr on 5/7/17. |
||||||
|
*/ |
||||||
|
public class BookmarkExporter { |
||||||
|
|
||||||
|
private static final String TAG = "BookmarkExporter"; |
||||||
|
|
||||||
|
private static final String KEY_URL = "url"; |
||||||
|
private static final String KEY_TITLE = "title"; |
||||||
|
private static final String KEY_FOLDER = "folder"; |
||||||
|
private static final String KEY_ORDER = "order"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Retrieves all the default bookmarks stored |
||||||
|
* in the raw file within assets. |
||||||
|
* |
||||||
|
* @param context the context necessary to open assets. |
||||||
|
* @return a non null list of the bookmarks stored in assets. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public static List<HistoryItem> importBookmarksFromAssets(@NonNull Context context) { |
||||||
|
List<HistoryItem> bookmarks = new ArrayList<>(); |
||||||
|
BufferedReader bookmarksReader = null; |
||||||
|
InputStream inputStream = null; |
||||||
|
try { |
||||||
|
inputStream = context.getResources().openRawResource(R.raw.default_bookmarks); |
||||||
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||||
|
bookmarksReader = new BufferedReader(new InputStreamReader(inputStream)); |
||||||
|
String line; |
||||||
|
while ((line = bookmarksReader.readLine()) != null) { |
||||||
|
try { |
||||||
|
JSONObject object = new JSONObject(line); |
||||||
|
HistoryItem item = new HistoryItem(); |
||||||
|
item.setTitle(object.getString(KEY_TITLE)); |
||||||
|
final String url = object.getString(KEY_URL); |
||||||
|
item.setUrl(url); |
||||||
|
item.setFolder(object.getString(KEY_FOLDER)); |
||||||
|
item.setPosition(object.getInt(KEY_ORDER)); |
||||||
|
item.setImageId(R.drawable.ic_bookmark); |
||||||
|
bookmarks.add(item); |
||||||
|
} catch (JSONException e) { |
||||||
|
Log.e(TAG, "Can't parse line " + line, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "Error reading the bookmarks file", e); |
||||||
|
} finally { |
||||||
|
Utils.close(bookmarksReader); |
||||||
|
Utils.close(inputStream); |
||||||
|
} |
||||||
|
|
||||||
|
return bookmarks; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Exports the list of bookmarks to a file. |
||||||
|
* |
||||||
|
* @param bookmarkList the bookmarks to export. |
||||||
|
* @param file the file to export to. |
||||||
|
* @return an observable that emits a completion |
||||||
|
* event when the export is complete, or an error |
||||||
|
* event if there is a problem. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public static Completable exportBookmarksToFile(@NonNull final List<HistoryItem> bookmarkList, |
||||||
|
@NonNull final File file) { |
||||||
|
return Completable.create(new CompletableAction() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull final CompletableSubscriber subscriber) { |
||||||
|
Preconditions.checkNonNull(bookmarkList); |
||||||
|
BufferedWriter bookmarkWriter = null; |
||||||
|
try { |
||||||
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||||
|
bookmarkWriter = new BufferedWriter(new FileWriter(file, false)); |
||||||
|
|
||||||
|
JSONObject object = new JSONObject(); |
||||||
|
for (HistoryItem item : bookmarkList) { |
||||||
|
object.put(KEY_TITLE, item.getTitle()); |
||||||
|
object.put(KEY_URL, item.getUrl()); |
||||||
|
object.put(KEY_FOLDER, item.getFolder()); |
||||||
|
object.put(KEY_ORDER, item.getPosition()); |
||||||
|
bookmarkWriter.write(object.toString()); |
||||||
|
bookmarkWriter.newLine(); |
||||||
|
} |
||||||
|
subscriber.onComplete(); |
||||||
|
} catch (@NonNull IOException | JSONException e) { |
||||||
|
subscriber.onError(e); |
||||||
|
} finally { |
||||||
|
Utils.close(bookmarkWriter); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Attempts to import bookmarks from the |
||||||
|
* given file. If the file is not in a |
||||||
|
* supported format, it will fail. |
||||||
|
* |
||||||
|
* @param file the file to import from. |
||||||
|
* @return an observable that emits the |
||||||
|
* imported bookmarks, or an error if the |
||||||
|
* file cannot be imported. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
public static Single<List<HistoryItem>> importBookmarksFromFile(@NonNull final File file) { |
||||||
|
return Single.create(new SingleAction<List<HistoryItem>>() { |
||||||
|
@Override |
||||||
|
public void onSubscribe(@NonNull SingleSubscriber<List<HistoryItem>> subscriber) { |
||||||
|
BufferedReader bookmarksReader = null; |
||||||
|
try { |
||||||
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||||
|
bookmarksReader = new BufferedReader(new FileReader(file)); |
||||||
|
String line; |
||||||
|
|
||||||
|
List<HistoryItem> bookmarks = new ArrayList<>(); |
||||||
|
while ((line = bookmarksReader.readLine()) != null) { |
||||||
|
JSONObject object = new JSONObject(line); |
||||||
|
HistoryItem item = new HistoryItem(); |
||||||
|
item.setTitle(object.getString(KEY_TITLE)); |
||||||
|
item.setUrl(object.getString(KEY_URL)); |
||||||
|
item.setFolder(object.getString(KEY_FOLDER)); |
||||||
|
item.setPosition(object.getInt(KEY_ORDER)); |
||||||
|
bookmarks.add(item); |
||||||
|
} |
||||||
|
|
||||||
|
subscriber.onItem(bookmarks); |
||||||
|
subscriber.onComplete(); |
||||||
|
} catch (IOException | JSONException e) { |
||||||
|
subscriber.onError(e); |
||||||
|
} finally { |
||||||
|
Utils.close(bookmarksReader); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* A blocking call that creates a new export file with |
||||||
|
* the name "BookmarkExport.txt" and an appropriate |
||||||
|
* numerical appendage if a file already exists with |
||||||
|
* that name. |
||||||
|
* |
||||||
|
* @return a non null empty file that can be used |
||||||
|
* to export bookmarks to. |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
@NonNull |
||||||
|
public static File createNewExportFile() { |
||||||
|
File bookmarksExport = new File( |
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), |
||||||
|
"BookmarksExport.txt"); |
||||||
|
int counter = 0; |
||||||
|
while (bookmarksExport.exists()) { |
||||||
|
counter++; |
||||||
|
bookmarksExport = new File( |
||||||
|
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), |
||||||
|
"BookmarksExport-" + counter + ".txt"); |
||||||
|
} |
||||||
|
|
||||||
|
return bookmarksExport; |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,164 @@ |
|||||||
|
package acr.browser.lightning.database.bookmark; |
||||||
|
|
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
import android.support.annotation.WorkerThread; |
||||||
|
|
||||||
|
import com.anthonycr.bonsai.Completable; |
||||||
|
import com.anthonycr.bonsai.Single; |
||||||
|
|
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import acr.browser.lightning.database.HistoryItem; |
||||||
|
|
||||||
|
/** |
||||||
|
* The interface that should be used to |
||||||
|
* communicate with the bookmark database. |
||||||
|
* <p> |
||||||
|
* Created by anthonycr on 5/6/17. |
||||||
|
*/ |
||||||
|
public interface BookmarkModel { |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the bookmark associated with the URL. |
||||||
|
* |
||||||
|
* @param url the URL to look for. |
||||||
|
* @return an observable that will emit either |
||||||
|
* the bookmark associated with the URL or null. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<HistoryItem> findBookmarkForUrl(@NonNull String url); |
||||||
|
|
||||||
|
/** |
||||||
|
* Determines if a URL is associated with a bookmark. |
||||||
|
* |
||||||
|
* @param url the URL to check. |
||||||
|
* @return an observable that will emit true if |
||||||
|
* the URL is a bookmark, false otherwise. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<Boolean> isBookmark(@NonNull String url); |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a bookmark if one does not already exist with |
||||||
|
* the same URL. |
||||||
|
* |
||||||
|
* @param item the bookmark to add. |
||||||
|
* @return an observable that emits true if the bookmark |
||||||
|
* was added, false otherwise. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<Boolean> addBookmarkIfNotExists(@NonNull HistoryItem item); |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds a list of bookmarks to the database. |
||||||
|
* |
||||||
|
* @param bookmarkItems the bookmarks to add. |
||||||
|
* @return an observable that emits a complete event |
||||||
|
* when all the bookmarks have been added. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Completable addBookmarkList(@NonNull List<HistoryItem> bookmarkItems); |
||||||
|
|
||||||
|
/** |
||||||
|
* Deletes a bookmark from the database. |
||||||
|
* |
||||||
|
* @param bookmark the bookmark to delete. |
||||||
|
* @return an observable that emits true when |
||||||
|
* the bookmark is deleted, false otherwise. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<Boolean> deleteBookmark(@NonNull HistoryItem bookmark); |
||||||
|
|
||||||
|
/** |
||||||
|
* Moves all bookmarks in the old folder to the new folder. |
||||||
|
* |
||||||
|
* @param oldName the name of the old folder. |
||||||
|
* @param newName the name of the new folder. |
||||||
|
* @return an observable that emits a completion |
||||||
|
* event when the folder is renamed. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Completable renameFolder(@NonNull String oldName, @NonNull String newName); |
||||||
|
|
||||||
|
/** |
||||||
|
* Deletes a folder from the database, all bookmarks |
||||||
|
* in that folder will be moved to the root level. |
||||||
|
* |
||||||
|
* @param folderToDelete the folder to delete. |
||||||
|
* @return an observable that emits a completion |
||||||
|
* event when the folder has been deleted. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Completable deleteFolder(@NonNull String folderToDelete); |
||||||
|
|
||||||
|
/** |
||||||
|
* Deletes all bookmarks in the database. |
||||||
|
* |
||||||
|
* @return an observable that emits a completion |
||||||
|
* event when all bookmarks have been deleted. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Completable deleteAllBookmarks(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Changes the bookmark with the original URL |
||||||
|
* with all the data from the new bookmark. |
||||||
|
* |
||||||
|
* @param oldBookmark the old bookmark to replace. |
||||||
|
* @param newBookmark the new bookmark. |
||||||
|
* @return an observable that emits a completion event |
||||||
|
* when the bookmark edit is done. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Completable editBookmark(@NonNull HistoryItem oldBookmark, @NonNull HistoryItem newBookmark); |
||||||
|
|
||||||
|
/** |
||||||
|
* Emits a list of all bookmarks |
||||||
|
* |
||||||
|
* @return an observable that emits a list |
||||||
|
* of all bookmarks. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<List<HistoryItem>> getAllBookmarks(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Emits all bookmarks in a certain folder. |
||||||
|
* If the folder chosen is null, then all bookmarks |
||||||
|
* without a specified folder will be returned. |
||||||
|
* |
||||||
|
* @param folder gets the bookmarks from this folder, may be null. |
||||||
|
* @return an observable that emits a list of bookmarks |
||||||
|
* in the given folder. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<List<HistoryItem>> getBookmarksFromFolder(@Nullable String folder); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all folders as {@link HistoryItem}. |
||||||
|
* The root folder is omitted. |
||||||
|
* |
||||||
|
* @return an observable that emits a list of folders. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<List<HistoryItem>> getFolders(); |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the names of all folders. |
||||||
|
* The root folder is omitted. |
||||||
|
* |
||||||
|
* @return an observable that emits a list of folder names. |
||||||
|
*/ |
||||||
|
@NonNull |
||||||
|
Single<List<String>> getFolderNames(); |
||||||
|
|
||||||
|
/** |
||||||
|
* A synchronous call to the model |
||||||
|
* that returns the number of bookmarks. |
||||||
|
* Should be called from a background thread. |
||||||
|
* |
||||||
|
* @return the number of bookmarks in the database. |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
long count(); |
||||||
|
} |
@ -0,0 +1,112 @@ |
|||||||
|
package acr.browser.lightning.database.bookmark.legacy; |
||||||
|
|
||||||
|
import android.app.Application; |
||||||
|
import android.support.annotation.NonNull; |
||||||
|
import android.support.annotation.Nullable; |
||||||
|
import android.support.annotation.WorkerThread; |
||||||
|
import android.util.Log; |
||||||
|
|
||||||
|
import org.json.JSONException; |
||||||
|
import org.json.JSONObject; |
||||||
|
|
||||||
|
import java.io.BufferedReader; |
||||||
|
import java.io.File; |
||||||
|
import java.io.FileInputStream; |
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.io.InputStreamReader; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Locale; |
||||||
|
|
||||||
|
import acr.browser.lightning.R; |
||||||
|
import acr.browser.lightning.database.HistoryItem; |
||||||
|
import acr.browser.lightning.utils.Utils; |
||||||
|
|
||||||
|
@Deprecated |
||||||
|
public class LegacyBookmarkManager { |
||||||
|
|
||||||
|
private static final String TAG = "LegacyBookmarkManager"; |
||||||
|
|
||||||
|
private static final String TITLE = "title"; |
||||||
|
private static final String URL = "url"; |
||||||
|
private static final String FOLDER = "folder"; |
||||||
|
private static final String ORDER = "order"; |
||||||
|
private static final String FILE_BOOKMARKS = "bookmarks.dat"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets all bookmarks from the old bookmark file |
||||||
|
* and then deletes the file. |
||||||
|
* |
||||||
|
* @param application the context needed to open the file. |
||||||
|
* @return a list of bookmarks from the old bookmark file. |
||||||
|
*/ |
||||||
|
@WorkerThread |
||||||
|
@NonNull |
||||||
|
public static List<HistoryItem> destructiveGetBookmarks(@NonNull Application application) { |
||||||
|
File filesDir = application.getFilesDir(); |
||||||
|
List<HistoryItem> bookmarks = new ArrayList<>(); |
||||||
|
final File bookmarksFile = new File(filesDir, FILE_BOOKMARKS); |
||||||
|
|
||||||
|
BufferedReader bookmarksReader = null; |
||||||
|
InputStream inputStream = null; |
||||||
|
try { |
||||||
|
if (bookmarksFile.exists() && bookmarksFile.isFile()) { |
||||||
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||||
|
inputStream = new FileInputStream(bookmarksFile); |
||||||
|
} else { |
||||||
|
return bookmarks; |
||||||
|
} |
||||||
|
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||||
|
bookmarksReader = new BufferedReader(new InputStreamReader(inputStream)); |
||||||
|
String line; |
||||||
|
while ((line = bookmarksReader.readLine()) != null) { |
||||||
|
try { |
||||||
|
JSONObject object = new JSONObject(line); |
||||||
|
|
||||||
|
HistoryItem item = new HistoryItem(); |
||||||
|
|
||||||
|
item.setTitle(object.getString(TITLE)); |
||||||
|
item.setUrl(object.getString(URL)); |
||||||
|
item.setFolder(object.getString(FOLDER)); |
||||||
|
item.setPosition(object.getInt(ORDER)); |
||||||
|
item.setImageId(R.drawable.ic_bookmark); |
||||||
|
|
||||||
|
bookmarks.add(item); |
||||||
|
} catch (JSONException e) { |
||||||
|
Log.e(TAG, "Can't parse line " + line, e); |
||||||
|
} |
||||||
|
} |
||||||
|
} catch (IOException e) { |
||||||
|
Log.e(TAG, "Error reading the bookmarks file", e); |
||||||
|
} finally { |
||||||
|
Utils.close(bookmarksReader); |
||||||
|
Utils.close(inputStream); |
||||||
|
} |
||||||
|
|
||||||
|
bookmarksFile.delete(); |
||||||
|
|
||||||
|
return bookmarks; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* This class sorts bookmarks alphabetically, with folders coming after bookmarks |
||||||
|
*/ |
||||||
|
private static class SortIgnoreCase implements Comparator<HistoryItem> { |
||||||
|
|
||||||
|
public int compare(@Nullable HistoryItem o1, @Nullable HistoryItem o2) { |
||||||
|
if (o1 == null || o2 == null) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
if (o1.isFolder() == o2.isFolder()) { |
||||||
|
return o1.getTitle().toLowerCase(Locale.getDefault()) |
||||||
|
.compareTo(o2.getTitle().toLowerCase(Locale.getDefault())); |
||||||
|
|
||||||
|
} else { |
||||||
|
return o1.isFolder() ? 1 : -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue