diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 0000000..ca1a55d --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,25 @@ +BookmarkHub Auto-Sync +Copyright 2024 Maciej Pindela + +This product includes software originally developed as BookmarkHub +Copyright (c) dudor (https://github.com/dudor/BookmarkHub) + +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. + +================================================================================ +MODIFICATIONS: +- Added real-time auto-sync functionality +- Implemented visual sync status indicators +- Improved sync conflict resolution logic +- Removed popup notifications in favor of badge indicators +================================================================================ \ No newline at end of file diff --git a/README.md b/README.md index 9a835a1..779de17 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,11 @@ BookmarkHub -

BookmarkHub

+

BookmarkHub Auto-Sync

- BookmarkHub is a browser plug-in that can synchronize your bookmarks between different browsers. + BookmarkHub with real-time auto-sync - automatically syncs your bookmarks when changes are detected. +
+ Based on original BookmarkHub by dudor
Feedback · diff --git a/bookmarkhub-0.0.6-firefox.zip b/bookmarkhub-1.0.1-chrome.zip similarity index 83% rename from bookmarkhub-0.0.6-firefox.zip rename to bookmarkhub-1.0.1-chrome.zip index d30d988..b74db28 100644 Binary files a/bookmarkhub-0.0.6-firefox.zip and b/bookmarkhub-1.0.1-chrome.zip differ diff --git a/bookmarkhub-0.0.6-chrome.zip b/bookmarkhub-1.0.1-firefox.zip similarity index 83% rename from bookmarkhub-0.0.6-chrome.zip rename to bookmarkhub-1.0.1-firefox.zip index ebaca38..7f04123 100644 Binary files a/bookmarkhub-0.0.6-chrome.zip and b/bookmarkhub-1.0.1-firefox.zip differ diff --git a/bookmarkhub-0.0.6-sources.zip b/bookmarkhub-1.0.1-sources.zip similarity index 97% rename from bookmarkhub-0.0.6-sources.zip rename to bookmarkhub-1.0.1-sources.zip index a1a7cae..88208a3 100644 Binary files a/bookmarkhub-0.0.6-sources.zip and b/bookmarkhub-1.0.1-sources.zip differ diff --git a/chrome-extension-build/manifest.json b/chrome-extension-build/manifest.json index 0fbebc2..5a4c37a 100644 --- a/chrome-extension-build/manifest.json +++ b/chrome-extension-build/manifest.json @@ -1 +1 @@ -{"manifest_version":3,"name":"__MSG_extensionName__","description":"__MSG_extensionDescription__","version":"1.0.0","icons":{"16":"icons/16.png","32":"icons/32.png","48":"icons/48.png","128":"icons/128.png"},"default_locale":"en","permissions":["storage","bookmarks","notifications"],"host_permissions":["https://*.github.com/","https://*.githubusercontent.com/"],"optional_host_permissions":["*://*/*"],"background":{"service_worker":"background.js"},"action":{"default_popup":"popup.html"},"options_ui":{"open_in_tab":false,"page":"options.html"}} \ No newline at end of file +{"manifest_version":3,"name":"__MSG_extensionName__","description":"__MSG_extensionDescription__","version":"1.0.1","icons":{"16":"icons/16.png","32":"icons/32.png","48":"icons/48.png","128":"icons/128.png"},"default_locale":"en","permissions":["storage","bookmarks","notifications"],"host_permissions":["https://*.github.com/","https://*.githubusercontent.com/"],"optional_host_permissions":["*://*/*"],"background":{"service_worker":"background.js"},"action":{"default_popup":"popup.html"},"options_ui":{"open_in_tab":false,"page":"options.html"}} \ No newline at end of file diff --git a/create_icon.py b/create_icon.py deleted file mode 100644 index e4ab021..0000000 --- a/create_icon.py +++ /dev/null @@ -1,82 +0,0 @@ -#!/usr/bin/env python3 -from PIL import Image, ImageDraw, ImageFont -import os - -def create_sync_icon(size, output_path): - """Create a sync icon with circular arrows""" - # Create a new image with transparent background - img = Image.new('RGBA', (size, size), (0, 0, 0, 0)) - draw = ImageDraw.Draw(img) - - # Define colors - primary_color = (66, 133, 244, 255) # Google Blue - secondary_color = (52, 168, 83, 255) # Google Green - - # Calculate dimensions - margin = size // 8 - center_x = size // 2 - center_y = size // 2 - radius = (size - 2 * margin) // 2 - - # Draw bookmark shape (simplified book icon) - book_width = size // 3 - book_height = size // 2.5 - book_x = center_x - book_width // 2 - book_y = center_y - book_height // 2 - - # Draw book shape - draw.rounded_rectangle( - [book_x, book_y, book_x + book_width, book_y + book_height], - radius=3, - fill=primary_color - ) - - # Draw sync arrows around the book - arrow_thickness = max(2, size // 32) - - # Top-right arrow (clockwise) - arrow_start_x = book_x + book_width + 5 - arrow_start_y = book_y - arrow_end_x = arrow_start_x + size // 8 - arrow_end_y = book_y + size // 8 - - # Draw curved arrow using arc - arc_box = [book_x - 10, book_y - 10, book_x + book_width + 10, book_y + book_height + 10] - draw.arc(arc_box, start=270, end=90, fill=secondary_color, width=arrow_thickness) - - # Draw arrow heads - # Top arrow head - arrow_size = max(4, size // 16) - draw.polygon([ - (book_x + book_width + 8, book_y), - (book_x + book_width + 8 + arrow_size, book_y - arrow_size // 2), - (book_x + book_width + 8 + arrow_size, book_y + arrow_size // 2) - ], fill=secondary_color) - - # Bottom arrow head - draw.polygon([ - (book_x - 8, book_y + book_height), - (book_x - 8 - arrow_size, book_y + book_height - arrow_size // 2), - (book_x - 8 - arrow_size, book_y + book_height + arrow_size // 2) - ], fill=secondary_color) - - # Save the image - img.save(output_path, 'PNG') - print(f"Created {output_path}") - -# Create icons in different sizes -sizes = { - 'icon-16.png': 16, - 'icon-32.png': 32, - 'icon-48.png': 48, - 'icon-96.png': 96, - 'icon-128.png': 128, -} - -os.makedirs('public', exist_ok=True) - -for filename, size in sizes.items(): - output_path = os.path.join('public', filename) - create_sync_icon(size, output_path) - -print("All icons created successfully!") \ No newline at end of file diff --git a/package.json b/package.json index 659face..62099d2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "sync bookmarks" ], "private": true, - "version": "1.0.0", + "version": "1.0.2", "type": "module", "author": "dudor", "license": "", diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip b/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip new file mode 100644 index 0000000..a8c5816 Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-chrome.zip differ diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip b/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip new file mode 100644 index 0000000..d6314eb Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-firefox.zip differ diff --git a/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip b/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip new file mode 100644 index 0000000..26d4461 Binary files /dev/null and b/releases/v1.0.2/bookmarkhub-1.0.2-sources.zip differ diff --git a/src/entrypoints/background.ts b/src/entrypoints/background.ts index 7cd5da9..ca80bbc 100644 --- a/src/entrypoints/background.ts +++ b/src/entrypoints/background.ts @@ -10,13 +10,28 @@ export default defineBackground(() => { initializeAutoSync(); }); + // Also sync on browser startup (when browser opens) + browser.runtime.onStartup.addListener(() => { + console.log('Browser startup detected, initializing auto-sync...'); + initializeAutoSync(); + }); + let curOperType = OperType.NONE; let curBrowserType = BrowserType.CHROME; let autoSyncDebounceTimer: NodeJS.Timeout | null = null; + let autoSyncIntervalTimer: NodeJS.Timeout | null = null; let lastRemoteUpdateTime: number = 0; let isSyncing: boolean = false; const DEBOUNCE_DELAY = 3000; // 3 seconds debounce delay + // Load last remote update time from storage on startup + browser.storage.local.get('lastRemoteUpdateTime').then(result => { + if (result.lastRemoteUpdateTime) { + lastRemoteUpdateTime = result.lastRemoteUpdateTime; + console.log('Loaded last remote update time:', new Date(lastRemoteUpdateTime).toISOString()); + } + }); + // Icon states const ICON_SYNCED = "✓"; const ICON_NOT_SYNCED = "!"; @@ -407,21 +422,58 @@ export default defineBackground(() => { // Auto-sync functions async function initializeAutoSync() { - // Clear any existing debounce timer + // Clear any existing timers if (autoSyncDebounceTimer) { clearTimeout(autoSyncDebounceTimer); autoSyncDebounceTimer = null; } + if (autoSyncIntervalTimer) { + clearInterval(autoSyncIntervalTimer); + autoSyncIntervalTimer = null; + } const setting = await Setting.build(); // If auto-sync is enabled, perform an initial sync to get the latest state if (setting.enableAutoSync && setting.githubToken && setting.gistID) { - // Show synced state on startup (optimistic) - browser.action.setBadgeText({ text: ICON_SYNCED }); - browser.action.setBadgeBackgroundColor({ color: "#00AA00" }); + // Show syncing state on startup + browser.action.setBadgeText({ text: ICON_SYNCING }); + browser.action.setBadgeBackgroundColor({ color: "#0000FF" }); + // Perform initial sync to get the latest state await performAutoSync(); + + // Set up periodic sync to check for remote changes + // Use more frequent checks initially, then slower for battery efficiency + const syncIntervalMinutes = setting.autoSyncInterval || 5; // Default to 5 minutes + let currentIntervalMs = Math.min(60000, syncIntervalMinutes * 60 * 1000); // Start with 1 minute or configured interval + + // Initial quick check after 30 seconds to catch immediate changes + setTimeout(async () => { + if (!isSyncing) { + await checkForRemoteChanges(); + } + }, 30000); + + // Set up regular interval + autoSyncIntervalTimer = setInterval(async () => { + // Only check for remote changes if not already syncing + if (!isSyncing) { + await checkForRemoteChanges(); + } + + // Gradually increase interval up to the configured max + if (currentIntervalMs < syncIntervalMinutes * 60 * 1000) { + clearInterval(autoSyncIntervalTimer!); + currentIntervalMs = Math.min(currentIntervalMs * 2, syncIntervalMinutes * 60 * 1000); + + autoSyncIntervalTimer = setInterval(async () => { + if (!isSyncing) { + await checkForRemoteChanges(); + } + }, currentIntervalMs); + } + }, currentIntervalMs); } } @@ -445,6 +497,67 @@ export default defineBackground(() => { }, DEBOUNCE_DELAY); } + async function checkForRemoteChanges() { + try { + const setting = await Setting.build(); + + // Check if auto-sync is still enabled and configured + if (!setting.enableAutoSync || !setting.githubToken || !setting.gistID) { + return; + } + + // Don't show syncing indicator for background checks to avoid UI noise + // Get remote bookmarks + const remoteGist = await BookmarkService.get(); + + if (remoteGist) { + const remoteData: SyncDataInfo = JSON.parse(remoteGist); + + // Get local bookmarks for comparison + const localBookmarks = await getBookmarks(); + const localData = formatBookmarks(localBookmarks); + const localContentHash = JSON.stringify(localData); + const remoteContentHash = JSON.stringify(remoteData.bookmarks); + + // If content differs and remote is newer, download changes + if (localContentHash !== remoteContentHash) { + // Check if remote has newer changes based on timestamp + if (!lastRemoteUpdateTime || remoteData.createDate > lastRemoteUpdateTime) { + console.log('Auto-sync: Detected newer remote changes, downloading...'); + + // Show syncing state + isSyncing = true; + browser.action.setBadgeText({ text: ICON_SYNCING }); + browser.action.setBadgeBackgroundColor({ color: "#0000FF" }); + curOperType = OperType.SYNC; + + // Download remote changes + await clearBookmarkTree(); + await createBookmarkTree(remoteData.bookmarks); + const remoteCount = getBookmarkCount(remoteData.bookmarks); + await browser.storage.local.set({ remoteCount: remoteCount }); + lastRemoteUpdateTime = remoteData.createDate; + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); + + // Show synced state + browser.action.setBadgeText({ text: ICON_SYNCED }); + browser.action.setBadgeBackgroundColor({ color: "#00AA00" }); + + curOperType = OperType.NONE; + isSyncing = false; + await refreshLocalCount(); + } + } else { + // Update timestamp even if content is the same + lastRemoteUpdateTime = remoteData.createDate; + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); + } + } + } catch (error) { + console.error('Check for remote changes error:', error); + } + } + async function performAutoSync() { try { const setting = await Setting.build(); @@ -474,11 +587,6 @@ export default defineBackground(() => { const remoteData: SyncDataInfo = JSON.parse(remoteGist); const remoteCount = getBookmarkCount(remoteData.bookmarks); - // Smart sync decision: - // 1. If we haven't tracked remote time yet, compare content - // 2. If remote was updated after our last known update AND content differs, download - // 3. Otherwise upload our changes - const localContentHash = JSON.stringify(localData); const remoteContentHash = JSON.stringify(remoteData.bookmarks); @@ -486,13 +594,16 @@ export default defineBackground(() => { // Already in sync, nothing to do console.log('Auto-sync: Already in sync'); lastRemoteUpdateTime = remoteData.createDate; - } else if (lastRemoteUpdateTime > 0 && remoteData.createDate > lastRemoteUpdateTime) { - // Remote has newer changes, download them - console.log('Auto-sync: Downloading newer remote changes'); + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); + } else if (remoteData.createDate > Date.now() - 10000) { + // If remote was updated in the last 10 seconds, it's probably from another browser + // Download those changes instead of overwriting + console.log('Auto-sync: Recent remote changes detected, downloading...'); await clearBookmarkTree(); await createBookmarkTree(remoteData.bookmarks); await browser.storage.local.set({ remoteCount: remoteCount }); lastRemoteUpdateTime = remoteData.createDate; + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); } else { // Upload local changes console.log('Auto-sync: Uploading local changes'); @@ -513,6 +624,7 @@ export default defineBackground(() => { await browser.storage.local.set({ remoteCount: localCount }); lastRemoteUpdateTime = syncdata.createDate; + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); } } else { // No remote data, upload initial @@ -534,6 +646,7 @@ export default defineBackground(() => { await browser.storage.local.set({ remoteCount: localCount }); lastRemoteUpdateTime = syncdata.createDate; + await browser.storage.local.set({ lastRemoteUpdateTime: lastRemoteUpdateTime }); } // Show synced icon (keep it persistent)