]> gitweb.stoutner.com Git - PrivacyBrowserAndroid.git/blob - app/src/main/java/com/stoutner/privacybrowser/helpers/ImportExportBookmarksHelper.kt
First wrong button text in View Headers in night theme. https://redmine.stoutner...
[PrivacyBrowserAndroid.git] / app / src / main / java / com / stoutner / privacybrowser / helpers / ImportExportBookmarksHelper.kt
1 /*
2  * Copyright 2023 Soren Stoutner <soren@stoutner.com>.
3  *
4  * This file is part of Privacy Browser Android <https://www.stoutner.com/privacy-browser-android>.
5  *
6  * Privacy Browser Android is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Privacy Browser Android is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Privacy Browser Android.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package com.stoutner.privacybrowser.helpers
21
22 import android.content.Context
23 import android.graphics.Bitmap
24 import android.graphics.drawable.BitmapDrawable
25 import android.net.Uri
26 import android.util.Base64
27 import android.widget.ScrollView
28
29 import androidx.appcompat.content.res.AppCompatResources
30
31 import com.google.android.material.snackbar.Snackbar
32 import com.stoutner.privacybrowser.BuildConfig
33
34 import com.stoutner.privacybrowser.R
35
36 import java.io.BufferedReader
37 import java.io.ByteArrayOutputStream
38 import java.io.InputStreamReader
39 import java.lang.Exception
40 import java.nio.charset.StandardCharsets
41
42 class ImportExportBookmarksHelper {
43     // Define the class variables.
44     private var bookmarksAndFolderExported = 0
45
46     fun importBookmarks(fileNameString: String, context: Context, scrollView: ScrollView) {
47         try {
48             // Get an input stream for the file name.
49             val inputStream = context.contentResolver.openInputStream(Uri.parse(fileNameString))!!
50
51             // Load the bookmarks input stream into a buffered reader.
52             val bufferedReader = BufferedReader(InputStreamReader(inputStream))
53
54             // Instantiate the bookmarks database helper.
55             val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
56
57             // Get the default icon drawables.
58             val defaultFavoriteIconDrawable = AppCompatResources.getDrawable(context, R.drawable.world)
59             val defaultFolderIconDrawable = AppCompatResources.getDrawable(context, R.drawable.folder_blue_bitmap)
60
61             // Cast the default icon drawables to bitmap drawables.
62             val defaultFavoriteIconBitmapDrawable = (defaultFavoriteIconDrawable as BitmapDrawable?)!!
63             val defaultFolderIconBitmapDrawable = (defaultFolderIconDrawable as BitmapDrawable)
64
65             // Get the default icon bitmaps.
66             val defaultFavoriteIconBitmap = defaultFavoriteIconBitmapDrawable.bitmap
67             val defaultFolderIconBitmap = defaultFolderIconBitmapDrawable.bitmap
68
69             // Create the default icon byte array output streams.
70             val defaultFavoriteIconByteArrayOutputStream = ByteArrayOutputStream()
71             val defaultFolderIconByteArrayOutputStream = ByteArrayOutputStream()
72
73             // Convert the default icon bitmaps to byte array streams.  `0` is for lossless compression (the only option for a PNG).
74             defaultFavoriteIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, defaultFavoriteIconByteArrayOutputStream)
75             defaultFolderIconBitmap.compress(Bitmap.CompressFormat.PNG, 0, defaultFolderIconByteArrayOutputStream)
76
77             // Convert the icon byte array streams to byte array stream to byte arrays.
78             val defaultFavoriteIconByteArray = defaultFavoriteIconByteArrayOutputStream.toByteArray()
79             val defaultFolderIconByteArray = defaultFolderIconByteArrayOutputStream.toByteArray()
80
81             // Get a cursor with all the bookmarks.
82             val initialNumberOfBookmarksAndFoldersCursor = bookmarksDatabaseHelper.allBookmarkAndFolderIds
83
84             // Get an initial count of the folders and bookmarks.
85             val initialNumberOfFoldersAndBookmarks = initialNumberOfBookmarksAndFoldersCursor.count
86
87             // Close the cursor.
88             initialNumberOfBookmarksAndFoldersCursor.close()
89
90             // Get a cursor with the contents of the home folder.
91             val homeFolderContentCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(0L)
92
93             // Initialize the variables.
94             var parentFolderId = 0L
95             var displayOrder = homeFolderContentCursor.count
96
97             // Close the cursor.
98             homeFolderContentCursor.close()
99
100             // Parse the bookmarks.
101             bufferedReader.forEachLine {
102                 // Trim the string.
103                 var line = it.trimStart()
104
105                 // Only process interesting lines.
106                 if (line.startsWith("<DT>")) {  // This is a bookmark or a folder.
107                     // Remove the initial `<DT>`
108                     line = line.substring(4)
109
110                     // Check to see if this is a bookmark or a folder.
111                     if (line.contains("HREF=\"")) {  // This is a bookmark.
112                         // Remove the text before the bookmark name.
113                         var bookmarkName = line.substring(line.indexOf(">") + 1)
114
115                         // Remove the text after the bookmark name.
116                         bookmarkName = bookmarkName.substring(0, bookmarkName.indexOf("<"))
117
118                         // Remove the text before the bookmark URL.
119                         var bookmarkUrl = line.substring(line.indexOf("HREF=\"") + 6)
120
121                         // Remove the text after the bookmark URL.
122                         bookmarkUrl = bookmarkUrl.substring(0, bookmarkUrl.indexOf("\""))
123
124                         // Initialize the favorite icon string.
125                         var favoriteIconString = ""
126
127                         // Populate the favorite icon string.
128                         if (line.contains("ICON=\"data:image/png;base64,")) {  // The `ICON` attribute contains a Base64 encoded icon.
129                             // Remove the text before the icon string.
130                             favoriteIconString = line.substring(line.indexOf("ICON=\"data:image/png;base64,") + 28)
131
132                             // Remove the text after the icon string.
133                             favoriteIconString = favoriteIconString.substring(0, favoriteIconString.indexOf("\""))
134                         } else if (line.contains("ICON_URI=\"data:image/png;base64,")) {  // The `ICON_URI` attribute contains a Base64 encoded icon.
135                             // Remove the text before the icon string.
136                             favoriteIconString = line.substring(line.indexOf("ICON_URI=\"data:image/png;base64,") + 32)
137
138                             // Remove the text after the icon string.
139                             favoriteIconString = favoriteIconString.substring(0, favoriteIconString.indexOf("\""))
140                         }
141
142                         // Populate the favorite icon byte array.
143                         val favoriteIconByteArray = if (favoriteIconString.isEmpty())  // The favorite icon string is empty.  Use the default favorite icon.
144                             defaultFavoriteIconByteArray
145                         else  // The favorite icon string is populated.  Decode it to a byte array.
146                             Base64.decode(favoriteIconString, Base64.DEFAULT)
147
148                         // Add the bookmark.
149                         bookmarksDatabaseHelper.createBookmark(bookmarkName, bookmarkUrl, parentFolderId, displayOrder, favoriteIconByteArray)
150
151                         // Increment the display order.
152                         ++displayOrder
153                     } else {  // This is a folder.  The following lines will be contain in this folder until a `</DL>` is encountered.
154                         // Remove the text before the folder name.
155                         var folderName = line.substring(line.indexOf(">") + 1)
156
157                         // Remove the text after the folder name.
158                         folderName = folderName.substring(0, folderName.indexOf("<"))
159
160                         // Add the folder and set it as the new parent folder ID.
161                         parentFolderId = bookmarksDatabaseHelper.createFolder(folderName, parentFolderId, displayOrder, defaultFolderIconByteArray)
162
163                         // Reset the display order.
164                         displayOrder = 0
165                     }
166                 } else if (line.startsWith("</DL>")) {  // This is the end of a folder's contents.
167                     // Reset the parent folder id if it isn't 0.
168                     if (parentFolderId != 0L)
169                         parentFolderId = bookmarksDatabaseHelper.getParentFolderId(parentFolderId)
170
171                     // Get a cursor with the contents of the new parent folder.
172                     val folderContentCursor = bookmarksDatabaseHelper.getBookmarkAndFolderIds(parentFolderId)
173
174                     // Reset the display order.
175                     displayOrder = folderContentCursor.count
176
177                     // Close the cursor.
178                     folderContentCursor.close()
179                 }
180             }
181
182             // Get a cursor with all the bookmarks.
183             val finalNumberOfBookmarksAndFoldersCursor = bookmarksDatabaseHelper.allBookmarkAndFolderIds
184
185             // Get the final count of the folders and bookmarks.
186             val finalNumberOfFoldersAndBookmarks = finalNumberOfBookmarksAndFoldersCursor.count
187
188             // Close the cursor.
189             finalNumberOfBookmarksAndFoldersCursor.close()
190
191             // Close the bookmarks database helper.
192             bookmarksDatabaseHelper.close()
193
194             // Close the input stream.
195             inputStream.close()
196
197             // Display a snackbar with the number of folders and bookmarks imported.
198             Snackbar.make(scrollView, context.getString(R.string.bookmarks_imported, finalNumberOfFoldersAndBookmarks - initialNumberOfFoldersAndBookmarks), Snackbar.LENGTH_LONG).show()
199         } catch (exception: Exception) {
200             // Display a snackbar with the error message.
201             Snackbar.make(scrollView, context.getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
202         }
203     }
204
205     fun exportBookmarks(fileNameString: String, context: Context, scrollView: ScrollView) {
206         // Create a bookmarks string builder
207         val bookmarksStringBuilder = StringBuilder()
208
209         // Populate the headers.
210         bookmarksStringBuilder.append("<!DOCTYPE NETSCAPE-Bookmark-file-1>")
211         bookmarksStringBuilder.append("\n\n")
212         bookmarksStringBuilder.append("<!-- These bookmarks were exported from Privacy Browser Android ${BuildConfig.VERSION_NAME}. -->")
213         bookmarksStringBuilder.append("\n\n")
214         bookmarksStringBuilder.append("<meta HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">")
215         bookmarksStringBuilder.append("\n\n")
216
217         // Begin the bookmarks.
218         bookmarksStringBuilder.append("<DL><p>")
219         bookmarksStringBuilder.append("\n")
220
221         // Instantiate the bookmarks database helper.
222         val bookmarksDatabaseHelper = BookmarksDatabaseHelper(context)
223
224         // Initialize the indent string.
225         val indentString = "    "
226
227         // Populate the bookmarks, starting with the home folder.
228         bookmarksStringBuilder.append(populateBookmarks(bookmarksDatabaseHelper, 0L, indentString))
229
230         // End the bookmarks.
231         bookmarksStringBuilder.append("</DL>")
232         bookmarksStringBuilder.append("\n")
233
234         try {
235             // Get an output stream for the file name, truncating any existing content.
236             val outputStream = context.contentResolver.openOutputStream(Uri.parse(fileNameString), "wt")!!
237
238             // Write the bookmarks string to the output stream.
239             outputStream.write(bookmarksStringBuilder.toString().toByteArray(StandardCharsets.UTF_8))
240
241             // Close the output stream.
242             outputStream.close()
243
244             // Display a snackbar with the number of folders and bookmarks exported.
245             Snackbar.make(scrollView, context.getString(R.string.bookmarks_exported, bookmarksAndFolderExported), Snackbar.LENGTH_LONG).show()
246         } catch (exception: Exception) {
247             // Display a snackbar with the error message.
248             Snackbar.make(scrollView, context.getString(R.string.error, exception), Snackbar.LENGTH_INDEFINITE).show()
249         }
250
251         // Close the bookmarks database helper.
252         bookmarksDatabaseHelper.close()
253     }
254
255     private fun populateBookmarks(bookmarksDatabaseHelper: BookmarksDatabaseHelper, folderId: Long, indentString: String): String {
256         // Create a bookmarks string builder.
257         val bookmarksStringBuilder = StringBuilder()
258
259         // Get all the bookmarks in the current folder.
260         val bookmarksCursor = bookmarksDatabaseHelper.getBookmarks(folderId)
261
262         // Process each bookmark.
263         while (bookmarksCursor.moveToNext()) {
264             if (bookmarksCursor.getInt(bookmarksCursor.getColumnIndexOrThrow(IS_FOLDER)) == 1) {  // This is a folder.
265                 // Export the folder.
266                 bookmarksStringBuilder.append(indentString)
267                 bookmarksStringBuilder.append("<DT><H3>")
268                 bookmarksStringBuilder.append(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_NAME)))
269                 bookmarksStringBuilder.append("</H3>")
270                 bookmarksStringBuilder.append("\n")
271
272                 // Begin the folder contents.
273                 bookmarksStringBuilder.append(indentString)
274                 bookmarksStringBuilder.append("<DL><p>")
275                 bookmarksStringBuilder.append("\n")
276
277                 // Populate the folder contents.
278                 bookmarksStringBuilder.append(populateBookmarks(bookmarksDatabaseHelper, bookmarksCursor.getLong(bookmarksCursor.getColumnIndexOrThrow(FOLDER_ID)), "    $indentString"))
279
280                 // End the folder contents.
281                 bookmarksStringBuilder.append(indentString)
282                 bookmarksStringBuilder.append("</DL><p>")
283                 bookmarksStringBuilder.append("\n")
284
285                 // Increment the bookmarks and folders exported counter.
286                 ++bookmarksAndFolderExported
287             } else {  // This is a bookmark.
288                 // Export the bookmark.
289                 bookmarksStringBuilder.append(indentString)
290                 bookmarksStringBuilder.append("<DT><A HREF=\"")
291                 bookmarksStringBuilder.append(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_URL)))
292                 bookmarksStringBuilder.append("\" ICON=\"data:image/png;base64,")
293                 bookmarksStringBuilder.append(Base64.encodeToString(bookmarksCursor.getBlob(bookmarksCursor.getColumnIndexOrThrow(FAVORITE_ICON)), Base64.NO_WRAP))
294                 bookmarksStringBuilder.append("\">")
295                 bookmarksStringBuilder.append(bookmarksCursor.getString(bookmarksCursor.getColumnIndexOrThrow(BOOKMARK_NAME)))
296                 bookmarksStringBuilder.append("</A>")
297                 bookmarksStringBuilder.append("\n")
298
299                 // Increment the bookmarks and folders exported counter.
300                 ++bookmarksAndFolderExported
301             }
302         }
303
304         // Return the bookmarks string.
305         return bookmarksStringBuilder.toString()
306     }
307 }