Skip to content

Instantly share code, notes, and snippets.

@helospark
Last active May 7, 2025 16:00
Show Gist options
  • Select an option

  • Save helospark/84493aaa70efe0a1e721d03531eb360e to your computer and use it in GitHub Desktop.

Select an option

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.
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