@@ -13,6 +13,7 @@ import { QMPManager } from "./qmp";
1313import { assert } from "@vueuse/core" ;
1414import { setIntervalImmediately } from "../utils/interval" ;
1515import { ComposePortEntry , PortManager } from "../utils/port" ;
16+ import { SnapshotManager } from "./snapshot" ;
1617
1718const nodeFetch : typeof import ( 'node-fetch' ) . default = require ( 'node-fetch' ) ;
1819const fs : typeof import ( 'fs' ) = require ( 'fs' ) ;
@@ -37,7 +38,7 @@ const presetApps: WinApp[] = [
3738 Icon : AppIcons [ InternalApps . WINDOWS_DESKTOP ] ,
3839 Source : "internal" ,
3940 Path : InternalApps . WINDOWS_DESKTOP ,
40- Usage : 0
41+ Usage : 0
4142 } ,
4243 {
4344 Name : "⚙️ Windows Explorer" ,
@@ -95,7 +96,7 @@ class AppManager {
9596 appCache : WinApp [ ] = [ ]
9697 appUsageCache : { [ key : string ] : number } = { } ;
9798 #wbConfig: WinboatConfig | null = null ;
98-
99+
99100 constructor ( ) {
100101 if ( ! fs . existsSync ( USAGE_PATH ) ) {
101102 fs . writeFileSync ( USAGE_PATH , "{}" ) ;
@@ -126,8 +127,8 @@ class AppManager {
126127 }
127128
128129 // Get the usage object that's on the disk
129- const fsUsage = Object . entries ( JSON . parse ( fs . readFileSync ( USAGE_PATH , 'utf-8' ) ) ) as any [ ] ;
130- this . appCache = [ ] ;
130+ const fsUsage = Object . entries ( JSON . parse ( fs . readFileSync ( USAGE_PATH , 'utf-8' ) ) ) as any [ ] ;
131+ this . appCache = [ ] ;
131132
132133 // Populate appCache with dummy WinApp object containing data from the disk
133134 for ( let i = 0 ; i < fsUsage . length ; i ++ ) {
@@ -227,13 +228,14 @@ export class Winboat {
227228 appMgr : AppManager | null = null ;
228229 qmpMgr : QMPManager | null = null ;
229230 portMgr : Ref < PortManager | null > = ref ( null ) ;
231+ snapshotMgr : SnapshotManager | null = null ;
230232
231233
232234 constructor ( ) {
233235 if ( Winboat . instance ) {
234236 return Winboat . instance ;
235237 }
236-
238+
237239 // This is a special interval which will never be destroyed
238240 this . #containerInterval = setInterval ( async ( ) => {
239241 const _containerStatus = await this . getContainerStatus ( ) ;
@@ -252,13 +254,43 @@ export class Winboat {
252254
253255 this . #wbConfig = new WinboatConfig ( ) ;
254256
257+ this . snapshotMgr = new SnapshotManager ( ) ;
258+
255259 this . appMgr = new AppManager ( ) ;
256260
257261 Winboat . instance = this ;
258262
259263 return Winboat . instance ;
260264 }
261265
266+ // Helper method to get storage folder
267+ getStorageInfo ( ) : { type : "volume" | "bind" ; path : string } {
268+ const compose = this . parseCompose ( ) ;
269+ const storageVol = compose . services . windows . volumes . find ( ( v ) => v . includes ( "/storage" ) ) ;
270+
271+ if ( ! storageVol ) {
272+ throw new Error ( "Storage volume not found in compose file" ) ;
273+ }
274+
275+ // Check if it's a named volume (eg. "data:/storage")
276+ if ( storageVol . startsWith ( "data:" ) ) {
277+ return { type : "volume" , path : "winboat_data" } ;
278+ }
279+
280+ // Bind mount otherwise (es. "/path/to/folder:/storage")
281+ return { type : "bind" , path : storageVol . split ( ":" ) [ 0 ] } ;
282+ }
283+
284+ /**
285+ * Recreates the SnapshotManager instance with updated configuration.
286+ * Call this when the snapshot path changes.
287+ */
288+ recreateSnapshotManager ( ) : void {
289+ logger . info ( "Recreating SnapshotManager with updated configuration..." ) ;
290+ this . snapshotMgr = new SnapshotManager ( ) ;
291+ logger . info ( "SnapshotManager recreated successfully" ) ;
292+ }
293+
262294 /**
263295 * Creates the intervals which rely on the Winboat Guest API.
264296 */
@@ -438,7 +470,7 @@ export class Winboat {
438470
439471 /**
440472 * Returns the host port that maps to the given guest port
441- *
473+ *
442474 * @param guestPort The port that gets looked up
443475 * @returns The host port that maps to the given guest port, or null if not found
444476 */
@@ -487,7 +519,7 @@ export class Winboat {
487519 // QMP either doesn't exist or is disconnected
488520 await this . #connectQMPManager( ) ;
489521 logger . info ( "[QMPInterval] Created new QMP Manager" ) ;
490-
522+
491523 } , QMP_WAIT_MS ) ;
492524 }
493525
@@ -567,15 +599,15 @@ export class Winboat {
567599 this . containerActionLoading . value = true ;
568600
569601 const composeFilePath = path . join ( WINBOAT_DIR , 'docker-compose.yml' ) ;
570-
602+
571603 if ( restart ) {
572604 // 1. Compose down the current container
573605 await execAsync ( `docker compose -f ${ composeFilePath } down` ) ;
574606 }
575607
576608 // 2. Create a backup directory if it doesn't exist
577609 const backupDir = path . join ( WINBOAT_DIR , 'backup' ) ;
578-
610+
579611 if ( ! fs . existsSync ( backupDir ) ) {
580612 fs . mkdirSync ( backupDir ) ;
581613 logger . info ( `Created compose backup dir: ${ backupDir } ` )
@@ -590,13 +622,13 @@ export class Winboat {
590622 const newComposeYAML = PrettyYAML . stringify ( composeConfig ) . replaceAll ( "null" , "" ) ;
591623 fs . writeFileSync ( composeFilePath , newComposeYAML , { encoding : 'utf8' } ) ;
592624 logger . info ( `Wrote new compose file to: ${ composeFilePath } ` ) ;
593-
625+
594626 if ( restart ) {
595627 // 5. Deploy the container with the new compose file
596628 await execAsync ( `docker compose -f ${ composeFilePath } up -d` ) ;
597629 remote . getCurrentWindow ( ) . reload ( ) ;
598630 }
599-
631+
600632 logger . info ( "Replace compose config completed, successfully deployed new container" ) ;
601633
602634 this . containerActionLoading . value = false ;
@@ -608,7 +640,7 @@ export class Winboat {
608640 // 1. Stop container
609641 await this . stopContainer ( ) ;
610642 console . info ( "Stopped container" ) ;
611-
643+
612644 // 2. Remove the container
613645 await execAsync ( "docker rm WinBoat" )
614646 console . info ( "Removed container" )
@@ -652,7 +684,7 @@ export class Winboat {
652684 const rdpHostPort = this . getHostPort ( GUEST_RDP_PORT ) ;
653685
654686 logger . info ( `Launching app: ${ app . Name } at path ${ app . Path } ` ) ;
655-
687+
656688 const freeRDPBin = await getFreeRDP ( ) ;
657689
658690 logger . info ( `Using FreeRDP Command: '${ freeRDPBin } '` ) ;
@@ -728,7 +760,7 @@ export class Winboat {
728760 : path . join ( remote . app . getAppPath ( ) , '..' , '..' , 'guest_server' , 'winboat_guest_server.zip' ) ;
729761
730762 logger . info ( "ZIP Path" , zipPath )
731-
763+
732764 // 4. Send the payload to the guest server, as a multipart/form-data with updateFile
733765 const formData = new FormData ( ) ;
734766 formData . append ( 'updateFile' , fs . createReadStream ( zipPath ) ) ;
@@ -747,7 +779,7 @@ export class Winboat {
747779 const resJson = await res . json ( ) as GuestServerUpdateResponse ;
748780 logger . info ( `Update params: ${ JSON . stringify ( resJson , null , 4 ) } ` ) ;
749781 logger . info ( "Successfully sent update payload to guest server" ) ;
750-
782+
751783 } catch ( e ) {
752784 logger . error ( "Failed to send update payload to guest server" ) ;
753785 logger . error ( e ) ;
@@ -774,4 +806,4 @@ export class Winboat {
774806 get hasQMPInterval ( ) {
775807 return this . #qmpInterval !== null ;
776808 }
777- }
809+ }
0 commit comments