@@ -11,12 +11,14 @@ const { startServer } = require('../server');
1111const SERVER_HOST = '127.0.0.1' ;
1212const BASE_PORT = 3000 ;
1313const PORT_FALLBACK_LIMIT = 20 ;
14+ const ONE_WEEK_MS = 7 * 24 * 60 * 60 * 1000 ;
1415
1516let currentPort = BASE_PORT ;
1617
1718let tray = null ;
1819let serverInstance = null ;
1920let updateCheckInProgress = false ;
21+ let backgroundCheckInProgress = false ;
2022
2123// Single-instance guard: second launch reuses the running server and opens the UI.
2224const gotLock = app . requestSingleInstanceLock ( ) ;
@@ -114,7 +116,7 @@ function checkForUpdatesWithFeedback() {
114116 } ) ;
115117 return ;
116118 }
117- if ( updateCheckInProgress ) {
119+ if ( updateCheckInProgress || backgroundCheckInProgress ) {
118120 showUpdateMessage ( {
119121 type : 'info' ,
120122 message : 'Update check already in progress' ,
@@ -174,11 +176,89 @@ function checkForUpdatesWithFeedback() {
174176 } ) ;
175177}
176178
179+ function checkForUpdatesInBackground ( ) {
180+ if ( ! app . isPackaged ) return ;
181+ if ( updateCheckInProgress || backgroundCheckInProgress ) return ;
182+ backgroundCheckInProgress = true ;
183+ autoUpdater . checkForUpdates ( )
184+ . catch ( ( error ) => {
185+ const detail = error && error . message ? error . message : String ( error ) ;
186+ console . error ( '[Updater] Background check failed:' , detail ) ;
187+ } )
188+ . finally ( ( ) => {
189+ backgroundCheckInProgress = false ;
190+ } ) ;
191+ }
192+
193+ async function getCachedUpdateFileName ( cacheDir ) {
194+ const infoPath = path . join ( cacheDir , 'update-info.json' ) ;
195+ try {
196+ const raw = await fs . promises . readFile ( infoPath , 'utf8' ) ;
197+ const info = JSON . parse ( raw ) ;
198+ if ( info && typeof info . fileName === 'string' && info . fileName ) {
199+ return info . fileName ;
200+ }
201+ } catch ( error ) {
202+ if ( ! ( error && error . code === 'ENOENT' ) ) {
203+ console . error ( '[Updater] Cache cleanup failed:' , error && error . message ? error . message : String ( error ) ) ;
204+ }
205+ }
206+ return null ;
207+ }
208+
209+ async function cleanupOldUpdateCaches ( { downloadedFile, pendingDir } ) {
210+ const cacheDir = pendingDir || ( downloadedFile ? path . dirname ( downloadedFile ) : null ) ;
211+ if ( ! cacheDir ) return ;
212+
213+ const updateFileName = downloadedFile
214+ ? path . basename ( downloadedFile )
215+ : await getCachedUpdateFileName ( cacheDir ) ;
216+ if ( ! updateFileName ) return ;
217+ const keepNames = new Set ( ) ;
218+ keepNames . add ( 'update-info.json' ) ;
219+ keepNames . add ( 'current.blockmap' ) ;
220+ keepNames . add ( updateFileName ) ;
221+ keepNames . add ( `${ updateFileName } .blockmap` ) ;
222+
223+ let entries = [ ] ;
224+ try {
225+ entries = await fs . promises . readdir ( cacheDir , { withFileTypes : true } ) ;
226+ } catch ( error ) {
227+ if ( error && error . code === 'ENOENT' ) return ;
228+ console . error ( '[Updater] Cache cleanup failed:' , error && error . message ? error . message : String ( error ) ) ;
229+ return ;
230+ }
231+
232+ const shouldKeep = keepNames . size > 0 ;
233+ await Promise . all ( entries . map ( async ( entry ) => {
234+ if ( shouldKeep && ( keepNames . has ( entry . name ) || entry . name . startsWith ( 'package-' ) ) ) return ;
235+ const entryPath = path . join ( cacheDir , entry . name ) ;
236+ try {
237+ await fs . promises . rm ( entryPath , { recursive : true , force : true } ) ;
238+ } catch ( error ) {
239+ console . error ( '[Updater] Cache cleanup failed:' , error && error . message ? error . message : String ( error ) ) ;
240+ }
241+ } ) ) ;
242+ }
243+
244+ async function cleanupUpdateCachesOnLaunch ( ) {
245+ if ( ! app . isPackaged ) return ;
246+ if ( typeof autoUpdater . getOrCreateDownloadHelper !== 'function' ) return ;
247+ try {
248+ const helper = await autoUpdater . getOrCreateDownloadHelper ( ) ;
249+ if ( ! helper || ! helper . cacheDirForPendingUpdate ) return ;
250+ await cleanupOldUpdateCaches ( { pendingDir : helper . cacheDirForPendingUpdate } ) ;
251+ } catch ( error ) {
252+ console . error ( '[Updater] Cache cleanup failed:' , error && error . message ? error . message : String ( error ) ) ;
253+ }
254+ }
255+
177256function setupAutoUpdater ( ) {
178257 // Updates only work for packaged builds with GitHub Releases.
179258 if ( ! app . isPackaged ) return ;
180259 autoUpdater . autoDownload = true ;
181- autoUpdater . on ( 'update-downloaded' , async ( ) => {
260+ cleanupUpdateCachesOnLaunch ( ) ;
261+ autoUpdater . on ( 'update-downloaded' , async ( info ) => {
182262 const result = await dialog . showMessageBox ( {
183263 type : 'info' ,
184264 buttons : [ 'Restart' , 'Later' ] ,
@@ -187,6 +267,7 @@ function setupAutoUpdater() {
187267 message : 'Update ready to install' ,
188268 detail : 'Restart the app to install the latest update.'
189269 } ) ;
270+ await cleanupOldUpdateCaches ( { downloadedFile : info && info . downloadedFile } ) ;
190271 if ( result . response === 0 ) {
191272 autoUpdater . quitAndInstall ( ) ;
192273 }
@@ -200,6 +281,9 @@ function setupAutoUpdater() {
200281async function boot ( ) {
201282 // Start local server, wait for readiness, then keep the tray ready for the user to open the UI.
202283 app . setAppUserModelId ( 'com.spacesoda.youtube2txt' ) ;
284+ if ( process . platform === 'darwin' && app . dock ) {
285+ app . dock . hide ( ) ;
286+ }
203287 setupAutoUpdater ( ) ;
204288
205289 const dataDir = ensureDataDir ( ) ;
@@ -241,7 +325,9 @@ async function boot() {
241325
242326 buildTray ( ) ;
243327 if ( app . isPackaged ) {
244- autoUpdater . checkForUpdatesAndNotify ( ) ;
328+ // Silent background check/download; UI remains driven by the tray action + update-downloaded prompt.
329+ checkForUpdatesInBackground ( ) ;
330+ setInterval ( checkForUpdatesInBackground , ONE_WEEK_MS ) ;
245331 }
246332}
247333
0 commit comments