diff --git a/macos/Tunarr/Tunarr.xcodeproj/project.xcworkspace/xcuserdata/cbeni.xcuserdatad/UserInterfaceState.xcuserstate b/macos/Tunarr/Tunarr.xcodeproj/project.xcworkspace/xcuserdata/cbeni.xcuserdatad/UserInterfaceState.xcuserstate new file mode 100644 index 00000000..68fc75cf Binary files /dev/null and b/macos/Tunarr/Tunarr.xcodeproj/project.xcworkspace/xcuserdata/cbeni.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/macos/Tunarr/Tunarr.xcodeproj/xcuserdata/cbeni.xcuserdatad/xcschemes/xcschememanagement.plist b/macos/Tunarr/Tunarr.xcodeproj/xcuserdata/cbeni.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 00000000..803d9d0c --- /dev/null +++ b/macos/Tunarr/Tunarr.xcodeproj/xcuserdata/cbeni.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,14 @@ + + + + + SchemeUserState + + Tunarr.xcscheme_^#shared#^_ + + orderHint + 0 + + + + diff --git a/macos/Tunarr/Tunarr/AppDelegate.swift b/macos/Tunarr/Tunarr/AppDelegate.swift index 1e5ac2e5..daf4f61a 100644 --- a/macos/Tunarr/Tunarr/AppDelegate.swift +++ b/macos/Tunarr/Tunarr/AppDelegate.swift @@ -6,31 +6,64 @@ // import AppKit +import OSLog import SwiftUI class AppDelegate: NSObject, NSApplicationDelegate { let bundle = Bundle.main + let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Tunarr Subprocess") var task = Process() func applicationDidFinishLaunching(_ notification: Notification) { guard let executablePath = Bundle.main.url(forAuxiliaryExecutable: "tunarr-macos") else { /// TODO: Do a popup here. - NSLog("Error: Bundled executable 'tunarr-macos' not found.") + logger.error("Error: Bundled executable 'tunarr-macos' not found.") + return + } + + guard let meilisearchPath = Bundle.main.url(forAuxiliaryExecutable: "meilisearch") + else { + logger.error("Error: Bundled executable 'meilisearch' not found") return } task.executableURL = executablePath + var env = ProcessInfo.processInfo.environment + env["TUNARR_MEILISEARCH_PATH"] = meilisearchPath.absoluteURL.path() + task.currentDirectoryURL = FileManager.default + .homeDirectoryForCurrentUser + .appendingPathComponent( + "Library" + ).appendingPathComponent("Preferences") + .appendingPathComponent("tunarr") + task.environment = env + logger.info("\(env, privacy: .public)") + + let errorPipe = Pipe() + task.standardError = errorPipe + do { + errorPipe.fileHandleForReading.readabilityHandler = { pipe in + let data = pipe.availableData + if let line = String(data: data, encoding: .utf8) { + if line.count > 0 { + self.logger.info( + "Process stderr: \(line.trimmingCharacters(in: .whitespacesAndNewlines), privacy: .public)" + ) + } + } + } try task.run() - NSLog("Successfully started Tunarr subprocess.") + + logger.info("Successfully started Tunarr subprocess.") } catch { - NSLog("Could not launch Tunarr. Reason: \(error)") + logger.error("Could not launch Tunarr. Reason: \(error, privacy: .public)") } } func applicationWillTerminate(_ aNotification: Notification) { - NSLog("Shutting down Tunarr.") + logger.info("Shutting down Tunarr.") task.terminate() task.waitUntilExit() } diff --git a/scripts/bundle-macos.sh b/scripts/bundle-macos.sh index ea2f01dc..661c19cf 100755 --- a/scripts/bundle-macos.sh +++ b/scripts/bundle-macos.sh @@ -19,5 +19,5 @@ cp -R "$REPO_ROOT/macos/Tunarr/build/Release/Tunarr.app" "$APP_NAME" popd || exit cp -a "$REPO_ROOT/server/bin/$BINARY_NAME" "$APP_NAME/Contents/MacOS/tunarr-macos" - -chmod +x "$APP_NAME/Contents/MacOS/tunarr-macos" \ No newline at end of file +cp -a "$REPO_ROOT/server/bin/meilisearch-macos-$2" "$APP_NAME/Contents/MacOS/meilisearch" +chmod +x "$APP_NAME/Contents/MacOS/tunarr-macos" "$APP_NAME/Contents/MacOS/meilisearch" \ No newline at end of file diff --git a/server/src/cli/RunServerCommand.ts b/server/src/cli/RunServerCommand.ts index 3eca559e..174c2d78 100644 --- a/server/src/cli/RunServerCommand.ts +++ b/server/src/cli/RunServerCommand.ts @@ -12,6 +12,7 @@ import { getNumericEnvVar, TUNARR_ENV_VARS, } from '../util/env.ts'; +import { LoggerFactory } from '../util/logging/LoggerFactory.ts'; import type { GlobalArgsType } from './types.ts'; export type ServerArgsType = GlobalArgsType & { @@ -57,6 +58,16 @@ export const RunServerCommand: CommandModule = { // port precedence - env var -> argument -> settings setServerOptions({ ...opts, port: portToUse }); + process.on('uncaughtException', (err) => { + LoggerFactory.root.error(err, 'Uncaught exception'); + LoggerFactory.root.flush(); + }); + + process.on('unhandledRejection', (err) => { + LoggerFactory.root.error(err, 'Uncaught exception'); + LoggerFactory.root.flush(); + }); + // Hard fail without database connection. assert(!!container.get(DBAccess).db); await container.get(App).start(); diff --git a/server/src/services/MeilisearchService.ts b/server/src/services/MeilisearchService.ts index 6d8b71f8..b6dc95b8 100644 --- a/server/src/services/MeilisearchService.ts +++ b/server/src/services/MeilisearchService.ts @@ -372,6 +372,8 @@ export class MeilisearchService implements ISearchService { '--db-path', `${this.dbPath}`, '--no-analytics', + '--log-level', + 'trace', ]; const indexingRamSetting = @@ -398,7 +400,10 @@ export class MeilisearchService implements ISearchService { if ( !isWindows() && - getBooleanEnvVar(TUNARR_ENV_VARS.DEBUG__REDUCE_SEARCH_INDEXING_MEMORY) + getBooleanEnvVar( + TUNARR_ENV_VARS.DEBUG__REDUCE_SEARCH_INDEXING_MEMORY, + os.platform() === 'linux', + ) ) { args.push('--experimental-reduce-indexing-memory-usage'); } @@ -439,6 +444,9 @@ export class MeilisearchService implements ISearchService { this.proc = await this.childProcessHelper.spawn(executablePath, args, { maxAttempts: 3, + additionalOpts: { + cwd: this.serverOptions.databaseDirectory, + }, }); this.logger.info('Meilisearch service started on port %d', this.port); const outStream = createWriteStream(searchServerLogFile); diff --git a/server/src/util/ChildProcessHelper.ts b/server/src/util/ChildProcessHelper.ts index b25ec4a9..bbd66e84 100644 --- a/server/src/util/ChildProcessHelper.ts +++ b/server/src/util/ChildProcessHelper.ts @@ -3,7 +3,11 @@ import { isNonEmptyString } from '@/util/index.js'; import { sanitizeForExec } from '@/util/strings.js'; import { inject, injectable } from 'inversify'; import { isEmpty } from 'lodash-es'; -import type { ChildProcessByStdio, ExecOptions } from 'node:child_process'; +import type { + ChildProcessByStdio, + ExecOptions, + SpawnOptions, +} from 'node:child_process'; import { execFile, spawn } from 'node:child_process'; import events from 'node:events'; import { Readable } from 'node:stream'; @@ -18,6 +22,7 @@ type SpawnOpts = { name: string; restartOnFailure: boolean; maxAttempts: number; + additionalOpts?: SpawnOptions; }; type ChildProcessEvents = { @@ -72,6 +77,7 @@ export class ChildProcessWrapper extends ITypedEventEmitter { this.logger.debug('Starting process: %s %O', this.path, this.args); const proc = spawn(this.path, this.args, { + ...(this.opts.additionalOpts ?? {}), stdio: ['ignore', 'pipe', 'pipe'], signal: this.controller.signal, env: this.env, @@ -102,6 +108,7 @@ export class ChildProcessWrapper extends ITypedEventEmitter { if (!this.wasAborted && code !== 0) { const bufferedBytes = bufferedOut.getLastN().toString('utf-8'); + this.logger.error(bufferedBytes); console.error(bufferedBytes); }