Last active
May 7, 2025 16:00
-
-
Save helospark/84493aaa70efe0a1e721d03531eb360e to your computer and use it in GitHub Desktop.
Saves firefox open tabs, so they are not lost in case Firefox crashes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.nio.file.NoSuchFileException; | |
| import java.nio.file.Path; | |
| import java.nio.file.Paths; | |
| import java.nio.file.StandardCopyOption; | |
| import java.nio.file.attribute.BasicFileAttributes; | |
| import java.time.LocalDate; | |
| import java.time.format.DateTimeFormatter; | |
| import java.util.Comparator; | |
| import java.util.List; | |
| import java.util.concurrent.Executors; | |
| import java.util.concurrent.ScheduledExecutorService; | |
| import java.util.concurrent.TimeUnit; | |
| import java.util.stream.Collectors; | |
| import java.util.stream.Stream; | |
| /** | |
| * Creates a backup for the firefox open tabs, so in case it's lost, it can easily be restored. | |
| * To restore backup, just copy the latest session_backups to the profile folder with name sessionstore.jsonlz4. | |
| * | |
| * To compile use: javac FirefoxBackup.java | |
| * and run: java FirefoxBackup | |
| * | |
| * Make sure it's always running by adding as automatically started program | |
| */ | |
| public class FirefoxBackup { | |
| private static final String homeDir = System.getProperty("user.home"); | |
| private static final String PROFILE_FOLDER_NAME = "FILL_OUT"; // TODO: fill out | |
| private static final Path PROFILE_PATH = Paths.get(homeDir, "snap", "firefox", "common", ".mozilla", "firefox", PROFILE_FOLDER_NAME); | |
| private static final String TARGET_FILENAME = "sessionstore.jsonlz4"; | |
| private static final Path TARGET_FILE_PATH = PROFILE_PATH.resolve(TARGET_FILENAME); | |
| private static final Path BACKUP_DIR_PATH = PROFILE_PATH.resolve("session_backups"); // Backups stored inside the profile dir | |
| private static final String BACKUP_PREFIX = "sessionstore-"; | |
| private static final String BACKUP_SUFFIX = ".jsonlz4"; | |
| private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); | |
| private static final int MAX_BACKUPS = 10; // Keep the last 10 backups | |
| private static final long CHECK_INTERVAL_MINUTES = 5; // Check every 5 minutes | |
| public static void main(String[] args) { | |
| System.out.println("Starting Firefox Session Backup Monitor..."); | |
| System.out.println("Profile Path: " + PROFILE_PATH); | |
| System.out.println("Target File: " + TARGET_FILE_PATH); | |
| System.out.println("Backup Directory: " + BACKUP_DIR_PATH); | |
| System.out.println("Check Interval: " + CHECK_INTERVAL_MINUTES + " minutes"); | |
| System.out.println("Max Backups: " + MAX_BACKUPS); | |
| // Use a scheduled executor to run the check periodically | |
| ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); | |
| scheduler.scheduleAtFixedRate(FirefoxBackup::performCheckAndBackup, 0, CHECK_INTERVAL_MINUTES, TimeUnit.MINUTES); | |
| // Keep the main thread alive (optional, needed if not running as a service) | |
| // try { | |
| // Thread.currentThread().join(); | |
| // } catch (InterruptedException e) { | |
| // Thread.currentThread().interrupt(); | |
| // System.err.println("Monitor interrupted."); | |
| // } | |
| } | |
| private static void performCheckAndBackup() { | |
| System.out.println("[" + java.time.LocalDateTime.now() + "] Checking for " + TARGET_FILENAME + "..."); | |
| if (Files.exists(TARGET_FILE_PATH)) { | |
| System.out.println(" -> Found " + TARGET_FILENAME); | |
| try { | |
| // Ensure backup directory exists | |
| if (!Files.exists(BACKUP_DIR_PATH)) { | |
| Files.createDirectories(BACKUP_DIR_PATH); | |
| System.out.println(" -> Created backup directory: " + BACKUP_DIR_PATH); | |
| } | |
| // Check if backup for today already exists | |
| String todayDateStr = LocalDate.now().format(DATE_FORMATTER); | |
| String todaysBackupFilename = BACKUP_PREFIX + todayDateStr + BACKUP_SUFFIX; | |
| Path todaysBackupPath = BACKUP_DIR_PATH.resolve(todaysBackupFilename); | |
| if (!Files.exists(todaysBackupPath)) { | |
| // Create today's backup | |
| Files.copy(TARGET_FILE_PATH, todaysBackupPath, StandardCopyOption.REPLACE_EXISTING); | |
| System.out.println(" -> Successfully created backup: " + todaysBackupFilename); | |
| // Clean up old backups | |
| cleanupOldBackups(); | |
| } else { | |
| System.out.println(" -> Backup for today (" + todaysBackupFilename + ") already exists. Skipping."); | |
| } | |
| } catch (IOException e) { | |
| System.err.println(" -> ERROR during backup process: " + e.getMessage()); | |
| e.printStackTrace(); // Print stack trace for detailed debugging | |
| } | |
| } else { | |
| System.out.println(" -> " + TARGET_FILENAME + " not found."); | |
| } | |
| } | |
| private static void cleanupOldBackups() { | |
| System.out.println(" -> Checking for old backups to clean up..."); | |
| try (Stream<Path> backupFilesStream = Files.list(BACKUP_DIR_PATH)) { | |
| List<Path> backupFiles = backupFilesStream | |
| .filter(p -> p.getFileName().toString().startsWith(BACKUP_PREFIX) && p.getFileName().toString().endsWith(BACKUP_SUFFIX)) | |
| .sorted(Comparator.comparing(FirefoxBackup::getFileCreationTime).reversed()) // Sort newest first | |
| .collect(Collectors.toList()); | |
| if (backupFiles.size() > MAX_BACKUPS) { | |
| System.out.println(" -> Found " + backupFiles.size() + " backups. Need to remove " + (backupFiles.size() - MAX_BACKUPS)); | |
| // Get the sublist of files to delete (oldest ones) | |
| List<Path> filesToDelete = backupFiles.subList(MAX_BACKUPS, backupFiles.size()); | |
| for (Path fileToDelete : filesToDelete) { | |
| try { | |
| Files.delete(fileToDelete); | |
| System.out.println(" -> Deleted old backup: " + fileToDelete.getFileName()); | |
| } catch (IOException e) { | |
| System.err.println(" -> ERROR deleting old backup " + fileToDelete.getFileName() + ": " + e.getMessage()); | |
| } | |
| } | |
| } else { | |
| System.out.println(" -> " + backupFiles.size() + " backups found. No cleanup needed."); | |
| } | |
| } catch (NoSuchFileException e) { | |
| System.out.println(" -> Backup directory doesn't exist yet. No cleanup needed."); | |
| } catch (IOException e) { | |
| System.err.println(" -> ERROR listing backup files for cleanup: " + e.getMessage()); | |
| } | |
| } | |
| // Helper method to get file creation time for sorting | |
| private static long getFileCreationTime(Path path) { | |
| try { | |
| BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class); | |
| // Use last modified time as a reliable sorting key across filesystems | |
| return attrs.lastModifiedTime().toMillis(); | |
| } catch (IOException e) { | |
| // Fallback or error handling | |
| System.err.println(" -> Warning: Could not read attributes for " + path + ". Using 0 for sorting."); | |
| return 0; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment