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);
}