X-Git-Url: https://gitweb.stoutner.com/?p=PrivacyBrowserAndroid.git;a=blobdiff_plain;f=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fhelpers%2FImportExportBookmarksHelper.kt;fp=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fstoutner%2Fprivacybrowser%2Fhelpers%2FImportExportBookmarksHelper.kt;h=92d4c7ccb22020e462f6d5fc04349e9501f792e3;hp=0000000000000000000000000000000000000000;hb=f9f282da2dfc2539c0880a6f9a07e17fa2e003d1;hpb=b75067d9fa0ab792f623e14e39628448eb8f6145 diff --git a/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportBookmarksHelper.kt b/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportBookmarksHelper.kt new file mode 100644 index 00000000..92d4c7cc --- /dev/null +++ b/app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportBookmarksHelper.kt @@ -0,0 +1,307 @@ +/* + * Copyright 2023 Soren Stoutner . + * + * This file is part of Privacy Browser Android . + * + * Privacy Browser Android is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Privacy Browser Android is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Privacy Browser Android. If not, see . + */ + +package com.stoutner.privacybrowser.helpers + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import android.util.Base64 +import android.widget.ScrollView + +import androidx.appcompat.content.res.AppCompatResources + +import com.google.android.material.snackbar.Snackbar +import com.stoutner.privacybrowser.BuildConfig + +import com.stoutner.privacybrowser.R + +import java.io.BufferedReader +import java.io.ByteArrayOutputStream +import java.io.InputStreamReader +import java.lang.Exception +import java.nio.charset.StandardCharsets + +class ImportExportBookmarksHelper { + // Define the class variables. + private var bookmarksAndFolderExported = 0 + + fun importBookmarks(fileNameString: String, context: Context, scrollView: ScrollView) { + try { + // Get an input stream for the file name. + val inputStream = context.contentResolver.openInputStream(Uri.parse(fileNameString))!! + + // Load the bookmarks input stream into a buffered reader. + val bufferedReader = BufferedReader(InputStreamReader(inputStream)) + + // Instantiate the bookmarks database helper. + val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context) + + // Get the default icon drawables. + val defaultFavoriteIconDrawable = AppCompatResources.getDrawable(context, R.drawable.world) + val defaultFolderIconDrawable = AppCompatResources.getDrawable(context, R.drawable.folder_blue_bitmap) + + // Cast the default icon drawables to bitmap drawables. + val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable?)!! + val defaultFolderIconBitmapDrawable = (defaultFolderIconDrawable as BitmapDrawable) + + // Get the default icon bitmaps. + val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap + val defaultFolderIconBitmap = defaultFolderIconBitmapDrawable.bitmap + + // Create the default icon byte array output streams. + val defaultFavoriteIconByteArrayOutputStream = ByteArrayOutputStream() + val defaultFolderIconByteArrayOutputStream = ByteArrayOutputStream() + + // Convert the default icon bitmaps to byte array streams. `0` is for lossless compression (the only option for a PNG). + defaultFavoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, defaultFavoriteIconByteArrayOutputStream) + defaultFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, defaultFolderIconByteArrayOutputStream) + + // Convert the icon byte array streams to byte array stream to byte arrays. + val defaultFavoriteIconByteArray = defaultFavoriteIconByteArrayOutputStream.toByteArray() + val defaultFolderIconByteArray = defaultFolderIconByteArrayOutputStream.toByteArray() + + // Get a cursor with all the bookmarks. + val initialNumberOfBookmarksAndFoldersCursor = bookmarksDatabaseHelper.allBookmarkAndFolderIds + + // Get an initial count of the folders and bookmarks. + val initialNumberOfFoldersAndBookmarks = initialNumberOfBookmarksAndFoldersCursor.count + + // Close the cursor. + initialNumberOfBookmarksAndFoldersCursor.close() + + // Get a cursor with the contents of the home folder. + val homeFolderContentCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(0L) + + // Initialize the variables. + var parentFolderId = 0L + var displayOrder = homeFolderContentCursor.count + + // Close the cursor. + homeFolderContentCursor.close() + + // Parse the bookmarks. + bufferedReader.forEachLine { + // Trim the string. + var line = it.trimStart() + + // Only process interesting lines. + if (line.startsWith("
")) { // This is a bookmark or a folder. + // Remove the initial `
` + line = line.substring(4) + + // Check to see if this is a bookmark or a folder. + if (line.contains("HREF=\"")) { // This is a bookmark. + // Remove the text before the bookmark name. + var bookmarkName = line.substring(line.indexOf(">") + 1) + + // Remove the text after the bookmark name. + bookmarkName = bookmarkName.substring(0, bookmarkName.indexOf("<")) + + // Remove the text before the bookmark URL. + var bookmarkUrl = line.substring(line.indexOf("HREF=\"") + 6) + + // Remove the text after the bookmark URL. + bookmarkUrl = bookmarkUrl.substring(0, bookmarkUrl.indexOf("\"")) + + // Initialize the favorite icon string. + var favoriteIconString = "" + + // Populate the favorite icon string. + if (line.contains("ICON=\"data:image/png;base64,")) { // The `ICON` attribute contains a Base64 encoded icon. + // Remove the text before the icon string. + favoriteIconString = line.substring(line.indexOf("ICON=\"data:image/png;base64,") + 28) + + // Remove the text after the icon string. + favoriteIconString = favoriteIconString.substring(0, favoriteIconString.indexOf("\"")) + } else if (line.contains("ICON_URI=\"data:image/png;base64,")) { // The `ICON_URI` attribute contains a Base64 encoded icon. + // Remove the text before the icon string. + favoriteIconString = line.substring(line.indexOf("ICON_URI=\"data:image/png;base64,") + 32) + + // Remove the text after the icon string. + favoriteIconString = favoriteIconString.substring(0, favoriteIconString.indexOf("\"")) + } + + // Populate the favorite icon byte array. + val favoriteIconByteArray = if (favoriteIconString.isEmpty()) // The favorite icon string is empty. Use the default favorite icon. + defaultFavoriteIconByteArray + else // The favorite icon string is populated. Decode it to a byte array. + Base64.decode(favoriteIconString, Base64.DEFAULT) + + // Add the bookmark. + bookmarksDatabaseHelper.createBookmark(bookmarkName, bookmarkUrl, parentFolderId, displayOrder, favoriteIconByteArray) + + // Increment the display order. + ++displayOrder + } else { // This is a folder. The following lines will be contain in this folder until a `` is encountered. + // Remove the text before the folder name. + var folderName = line.substring(line.indexOf(">") + 1) + + // Remove the text after the folder name. + folderName = folderName.substring(0, folderName.indexOf("<")) + + // Add the folder and set it as the new parent folder ID. + parentFolderId = bookmarksDatabaseHelper.createFolder(folderName, parentFolderId, displayOrder, defaultFolderIconByteArray) + + // Reset the display order. + displayOrder = 0 + } + } else if (line.startsWith("")) { // This is the end of a folder's contents. + // Reset the parent folder id if it isn't 0. + if (parentFolderId != 0L) + parentFolderId = bookmarksDatabaseHelper.getParentFolderId(parentFolderId) + + // Get a cursor with the contents of the new parent folder. + val folderContentCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(parentFolderId) + + // Reset the display order. + displayOrder = folderContentCursor.count + + // Close the cursor. + folderContentCursor.close() + } + } + + // Get a cursor with all the bookmarks. + val finalNumberOfBookmarksAndFoldersCursor = bookmarksDatabaseHelper.allBookmarkAndFolderIds + + // Get the final count of the folders and bookmarks. + val finalNumberOfFoldersAndBookmarks = finalNumberOfBookmarksAndFoldersCursor.count + + // Close the cursor. + finalNumberOfBookmarksAndFoldersCursor.close() + + // Close the bookmarks database helper. + bookmarksDatabaseHelper.close() + + // Close the input stream. + inputStream.close() + + // Display a snackbar with the number of folders and bookmarks imported. + Snackbar.make(scrollView, context.getString(R.string.bookmarks_imported, finalNumberOfFoldersAndBookmarks - initialNumberOfFoldersAndBookmarks), Snackbar.LENGTH_LONG).show() + } catch (exception: Exception) { + // Display a snackbar with the error message. + Snackbar.make(scrollView, context.getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show() + } + } + + fun exportBookmarks(fileNameString: String, context: Context, scrollView: ScrollView) { + // Create a bookmarks string builder + val bookmarksStringBuilder = StringBuilder() + + // Populate the headers. + bookmarksStringBuilder.append("") + bookmarksStringBuilder.append("\n\n") + bookmarksStringBuilder.append("") + bookmarksStringBuilder.append("\n\n") + bookmarksStringBuilder.append("") + bookmarksStringBuilder.append("\n\n") + + // Begin the bookmarks. + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append("\n") + + // Instantiate the bookmarks database helper. + val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context) + + // Initialize the indent string. + val indentString = " " + + // Populate the bookmarks, starting with the home folder. + bookmarksStringBuilder.append(populateBookmarks(bookmarksDatabaseHelper, 0L, indentString)) + + // End the bookmarks. + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append("\n") + + try { + // Get an output stream for the file name, truncating any existing content. + val outputStream = context.contentResolver.openOutputStream(Uri.parse(fileNameString), "wt")!! + + // Write the bookmarks string to the output stream. + outputStream.write(bookmarksStringBuilder.toString().toByteArray(StandardCharsets.UTF_8)) + + // Close the output stream. + outputStream.close() + + // Display a snackbar with the number of folders and bookmarks exported. + Snackbar.make(scrollView, context.getString(R.string.bookmarks_exported, bookmarksAndFolderExported), Snackbar.LENGTH_LONG).show() + } catch (exception: Exception) { + // Display a snackbar with the error message. + Snackbar.make(scrollView, context.getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show() + } + + // Close the bookmarks database helper. + bookmarksDatabaseHelper.close() + } + + private fun populateBookmarks(bookmarksDatabaseHelper: BookmarksDatabaseHelper, folderId: Long, indentString: String): String { + // Create a bookmarks string builder. + val bookmarksStringBuilder = StringBuilder() + + // Get all the bookmarks in the current folder. + val bookmarksCursor = bookmarksDatabaseHelper.getBookmarks(folderId) + + // Process each bookmark. + while (bookmarksCursor.moveToNext()) { + if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) { // This is a folder. + // Export the folder. + bookmarksStringBuilder.append(indentString) + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_NAME))) + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append("\n") + + // Begin the folder contents. + bookmarksStringBuilder.append(indentString) + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append("\n") + + // Populate the folder contents. + bookmarksStringBuilder.append(populateBookmarks(bookmarksDatabaseHelper, bookmarksCursor.getLong(bookmarksCursor.getColumnIndexOrThrow(FOLDER_ID)), " $indentString")) + + // End the folder contents. + bookmarksStringBuilder.append(indentString) + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append("\n") + + // Increment the bookmarks and folders exported counter. + ++bookmarksAndFolderExported + } else { // This is a bookmark. + // Export the bookmark. + bookmarksStringBuilder.append(indentString) + bookmarksStringBuilder.append("

") + bookmarksStringBuilder.append(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_NAME))) + bookmarksStringBuilder.append("") + bookmarksStringBuilder.append("\n") + + // Increment the bookmarks and folders exported counter. + ++bookmarksAndFolderExported + } + } + + // Return the bookmarks string. + return bookmarksStringBuilder.toString() + } +}