Integrate fossil SCM with Logseq-DB

Fossil SCM is a lightweight SCM system based on SQLite. I use it a lot. Though Logseq supports GIT and I also use GIT, it is too heavy to use git for personal users. I would like to see logseq-db can integrate fossil for providing version control features.

I just found a local-first and SQLite-based note-taking software, Trilium. Trilium supports note versioning, which is fascinating. I’d like to have this feauture in logseq-db. I think, based on SQLite and fossil, note-versioning might be an obvious feature to have.

I managed to integrate logseq and fossil with autohotkey. Using the script below:

What the scripts do:
Commit changes in graph folder to repo every 10m when logseq is running, with comment “A”.
If the logseq is exited , Commit changes in graph folder to repo, with comment “A”, then autohotkey script exit.
When ctrl-s is pressed when logseq is activated, commit changes in graph folder to repo, with comment “M”.
Update local graph from repo at logseq startup.

Benefits of using fossil scm:
Same benefits as git(work from multiple places with same graph, versioning, etc).
Light weight.
Fossil scm provides a builting web gui to do diff between versions

There are four scripts:
startlogseqandmonitor_v3.ahk
syncall2db_auto.bat
syncall2db_manual.bat
updatedb.bat

In theory, the batch commands can be integrated into ahk. But I failed to figure out how to do it after several hours.

The fossil scm repo for logseq graphs should be created at first and works with the batch files.

These scripts assume that logseq graphs is stored in <C:\Users\administrator\Documents\logseqdb\graphs>, fossil command stdout put is redirected to c:\temp

The fossil scm repo can be configured to sync with a remote fossil server for multiple places. Or just sync to local repo and then sync the repo files with other file sync service such as Syncthing.

These scripts can not handle all edge case, keep eyes on logs from time to time!

startlogseqandmonitor_v3.ahk

; Define application paths
app_a_exepath := "C:\Users\administrator\AppData\Local\Logseq\Logseq.exe"  ; Replace with the actual path of app_a
app_a_workingpath := "C:\Users\administrator\AppData\Local\Logseq\app-0.10.9"  ; Replace with the actual path of app_a

; Check if Logseq.exe is already running
Process, Exist, Logseq.exe
if (ErrorLevel > 0)
{
    ; If Logseq.exe is already running, get its PID
    PID_AppA := ErrorLevel
    MsgBox, Logseq.exe is already running. PID: %PID_AppA%
    CheckFossilLogs()
}
else
{
    ; If Logseq.exe is not running, sync and update the Logseq database with the remote database before starting Logseq
    SyncAndUpdateLogseqDB()
    CheckFossilLogs()

    ; Start Logseq
    Run, % app_a_exepath, % app_a_workingpath
    Sleep, 30000  ; Wait for 30 seconds to ensure app_a has started
    
    ; Get the actual PID of Logseq.exe
    Process, Exist, Logseq.exe
    PID_AppA := ErrorLevel
    if PID_AppA > 0
    {
        MsgBox, Logseq.exe has been started. PID: %PID_AppA%
    }
    else
    {
        MsgBox, Logseq.exe failed to start.
    }
}

; Feature 1: Check if app_a is running every 30 seconds and call app_b based on conditions
SetTimer, CheckAppAStatus, 600000  ; Check every 600 seconds

; Feature 2: Use #IfWinActive to ensure Ctrl+S only works when app_a is in the foreground. Note that #IfWinActive is a directive preprocessor instruction, which is parsed when the script is loaded, not dynamically at runtime. Therefore, variables (such as %PID_AppA%) cannot be directly used in #IfWinActive
#IfWinActive ahk_exe Logseq.exe
{
    ^s::  ; Ctrl+S
    SyncToDBManual()
    CheckFossilLogs()
    return
}
#IfWinActive  ; End condition directive

; Timer function to check the status of app_a
CheckAppAStatus:
    Process, Exist, %PID_AppA%
    if (ErrorLevel = 0)
    {
        ; If app_a is no longer running, call app_b and exit the script
        SyncToDBAuto()
        CheckFossilLogs()
        MsgBox, Logseq is no longer running. Exiting script.
        ExitApp
    }

    SyncToDBAuto()
    CheckFossilLogs()
    ; MsgBox, Auto commit every 10 minutes.
return

; Define a function to check the fossil logs in the temp directory
CheckFossilLogs()
{
    ; Define directory and search pattern
    dir := "c:\temp"
    pattern := "fossil*.log"

    ; Loop through the files in the directory
    Sleep 2000 ; wait for completion of preceding actions
    Loop, Files, %dir%\%pattern%, F
    {
        ; Open the file and read its content
        file_path := A_LoopFileFullPath
        FileRead, file_content, %file_path%
        ;    MsgBox, 0, Fossil Sync Failure, % file_content, 6

        ; Check if the file content contains "New_Version:" and display a tray icon notification
        if (InStr(file_content, "New_Version:", true) > 0)
        {
            TrayTip Logseq Sync, New Changes Committed
            Sleep 3000   ; Let it display for 3 seconds.
            HideTrayTip()
            ;TrayTip #2, This is the second notification.
            ;Sleep 3000
        }

        ; Check if the file content contains "sync failure"
        if (InStr(file_content, "sync failure", true) > 0)
        {
            ; Pop up a message box
            ;    MsgBox, 0, Fossil Sync Failure, % file_content, 6
            MsgBox, 0, Fossil Sync Failure, Remote fossil repo sync failure!, 6

            ; Forcefully terminate all running fossil.exe processes
            Process, Close, fossil.exe
        }
    }
}

; Define a function to perform sync and update operations
SyncAndUpdateLogseqDB()
{
    ; Define working directory and log file path
    app_exepath := "C:\Users\administrator\Documents\logseqdb\updatedb.bat"
    app_workingpath := "C:\Users\administrator\Documents\logseqdb"

    ; Execute the app command
    Run, % app_exepath, % app_workingpath, Hide
}

; Define a function to perform automatic commits at regular intervals
SyncToDBAuto()
{
    ; Define working directory and log file path
    app_exepath := "C:\Users\administrator\Documents\logseqdb\syncall2db_auto.bat"
    app_workingpath := "C:\Users\administrator\Documents\logseqdb"

    ; Execute the app command
    Run, % app_exepath, % app_workingpath, Hide
}

; Define a function to perform manual commits via Ctrl-S in the Logseq interface
SyncToDBManual()
{
    ; Define working directory and log file path
    app_exepath := "C:\Users\administrator\Documents\logseqdb\syncall2db_manual.bat"
    app_workingpath := "C:\Users\administrator\Documents\logseqdb"

    ; Execute the app command
    Run, % app_exepath, % app_workingpath, Hide
}

; Copy this function into your script to use it.
; https://documentation.help/AutoHotkey-en/TrayTip.htm
HideTrayTip() {
    ;TrayTip  ; Attempt to hide it the normal way.
    if SubStr(A_OSVersion,1,3) = "10." {
        Menu Tray, NoIcon
        Sleep 200  ; It may be necessary to adjust this sleep.
        Menu Tray, Icon
    }
}

syncall2db_auto.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil sync > c:\temp\fossil_sync.log
fossil update > c:\temp\fossil_update.log 

syncall2db_manual.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil addremove > c:\temp\fossil_addremove.log
fossil commit --no-prompt --no-warnings --comment "M" > c:\temp\fossil_commit.log

updatedb.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil addremove > c:\temp\fossil_addremove.log
fossil commit --no-prompt --no-warnings --comment "M" > c:\temp\fossil_commit.log
1 Like

Just found I have mistaken the contents of batch files, the right ones are below.

syncall2db_auto.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil addremove > c:\temp\fossil_addremove.log
fossil commit --no-prompt --no-warnings --comment "A" > c:\temp\fossil_commit.log

syncall2db_manual.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil addremove > c:\temp\fossil_addremove.log
fossil commit --no-prompt --no-warnings --comment "M" > c:\temp\fossil_commit.log

updatedb.bat

cd C:\Users\administrator\Documents\logseqdb\graphs\
fossil sync > c:\temp\fossil_sync.log
fossil update > c:\temp\fossil_update.log