@trillboards/edge-sdk 0.2.3 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -3
- package/deploy/docker/Dockerfile.cloud-sense +117 -0
- package/deploy/docker/Dockerfile.cpu +30 -21
- package/deploy/docker/Dockerfile.cuda +30 -21
- package/deploy/docker/Dockerfile.openvino +30 -20
- package/deploy/docker/README.md +9 -4
- package/deploy/docker/connector-entrypoint.sh +122 -0
- package/deploy/helm/README.md +1 -1
- package/deploy/k8s/daemonset.yaml +1 -1
- package/deploy/macos/README.md +161 -0
- package/deploy/macos/TrillboardsSetupApp.m +603 -0
- package/deploy/macos/build-macos-pkg.sh +588 -0
- package/deploy/macos/com.trillboards.edge.plist +62 -0
- package/deploy/macos/com.trillboards.litertlm.plist +45 -0
- package/deploy/macos/pair.command +519 -0
- package/deploy/macos/scripts/ensure-litertlm.sh +62 -0
- package/deploy/macos/scripts/postinstall +153 -0
- package/deploy/macos/scripts/setup-litertlm.sh +272 -0
- package/deploy/macos/test-install-pair.sh +383 -0
- package/deploy/macos/test-setup-litertlm-reinstall.sh +109 -0
- package/deploy/systemd/install.sh +25 -15
- package/deploy/systemd/trillboards-edge.service +4 -1
- package/deploy/windows/README.md +70 -0
- package/deploy/windows/build-windows.ps1 +225 -0
- package/deploy/windows/bundle-windows-installer.ps1 +153 -0
- package/deploy/windows/check-feeds.js +26 -0
- package/deploy/windows/e2e/fixtures/person.jpg +0 -0
- package/deploy/windows/e2e/test-local-sense.ps1 +185 -0
- package/deploy/windows/install-update.ps1 +101 -0
- package/deploy/windows/installer.nsi +138 -0
- package/deploy/windows/register-litertlm-task.ps1 +42 -0
- package/deploy/windows/register-task.ps1 +44 -0
- package/deploy/windows/run-connector.ps1 +69 -0
- package/deploy/windows/setup-litertlm.ps1 +361 -0
- package/deploy/windows/stop-existing.ps1 +72 -0
- package/deploy/windows/trillboards-edge-task.vbs +25 -0
- package/deploy/windows/trillboards-edge.cmd +33 -0
- package/deploy/windows/trillboards-pair.cmd +276 -0
- package/deploy/windows/trillboards-setup.ps1 +456 -0
- package/deploy/windows/trillboards-setup.vbs +13 -0
- package/dist/AudienceAnalyzeClient.d.ts +136 -0
- package/dist/AudienceAnalyzeClient.d.ts.map +1 -0
- package/dist/AudienceAnalyzeClient.js +246 -0
- package/dist/AudienceAnalyzeClient.js.map +1 -0
- package/dist/ChannelProber.d.ts +150 -0
- package/dist/ChannelProber.d.ts.map +1 -0
- package/dist/ChannelProber.js +887 -0
- package/dist/ChannelProber.js.map +1 -0
- package/dist/CloudEscalationPolicy.d.ts +143 -0
- package/dist/CloudEscalationPolicy.d.ts.map +1 -0
- package/dist/CloudEscalationPolicy.js +137 -0
- package/dist/CloudEscalationPolicy.js.map +1 -0
- package/dist/CloudSensingManager.d.ts +533 -0
- package/dist/CloudSensingManager.d.ts.map +1 -0
- package/dist/CloudSensingManager.js +1254 -0
- package/dist/CloudSensingManager.js.map +1 -0
- package/dist/DiscoveryReportClient.d.ts +66 -0
- package/dist/DiscoveryReportClient.d.ts.map +1 -0
- package/dist/DiscoveryReportClient.js +85 -0
- package/dist/DiscoveryReportClient.js.map +1 -0
- package/dist/EdgeAgent.d.ts +12 -2
- package/dist/EdgeAgent.d.ts.map +1 -1
- package/dist/EdgeAgent.js +122 -65
- package/dist/EdgeAgent.js.map +1 -1
- package/dist/LiteRtVlmProvider.d.ts +193 -0
- package/dist/LiteRtVlmProvider.d.ts.map +1 -0
- package/dist/LiteRtVlmProvider.js +436 -0
- package/dist/LiteRtVlmProvider.js.map +1 -0
- package/dist/MosaicSource.d.ts +108 -0
- package/dist/MosaicSource.d.ts.map +1 -0
- package/dist/MosaicSource.js +274 -0
- package/dist/MosaicSource.js.map +1 -0
- package/dist/OnvifClient.d.ts +83 -0
- package/dist/OnvifClient.d.ts.map +1 -0
- package/dist/OnvifClient.js +209 -0
- package/dist/OnvifClient.js.map +1 -0
- package/dist/PairClient.d.ts +106 -0
- package/dist/PairClient.d.ts.map +1 -0
- package/dist/PairClient.js +186 -0
- package/dist/PairClient.js.map +1 -0
- package/dist/RecorderAddressRecovery.d.ts +53 -0
- package/dist/RecorderAddressRecovery.d.ts.map +1 -0
- package/dist/RecorderAddressRecovery.js +359 -0
- package/dist/RecorderAddressRecovery.js.map +1 -0
- package/dist/SceneChangeDetector.d.ts +291 -0
- package/dist/SceneChangeDetector.d.ts.map +1 -0
- package/dist/SceneChangeDetector.js +682 -0
- package/dist/SceneChangeDetector.js.map +1 -0
- package/dist/SensingProfileClient.d.ts +59 -0
- package/dist/SensingProfileClient.d.ts.map +1 -0
- package/dist/SensingProfileClient.js +78 -0
- package/dist/SensingProfileClient.js.map +1 -0
- package/dist/SetupSessionClient.d.ts +114 -0
- package/dist/SetupSessionClient.d.ts.map +1 -0
- package/dist/SetupSessionClient.js +95 -0
- package/dist/SetupSessionClient.js.map +1 -0
- package/dist/VlmAdmissionGate.d.ts +126 -0
- package/dist/VlmAdmissionGate.d.ts.map +1 -0
- package/dist/VlmAdmissionGate.js +166 -0
- package/dist/VlmAdmissionGate.js.map +1 -0
- package/dist/autoSense.d.ts +216 -0
- package/dist/autoSense.d.ts.map +1 -0
- package/dist/autoSense.js +521 -0
- package/dist/autoSense.js.map +1 -0
- package/dist/cli.d.ts +27 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +511 -21
- package/dist/cli.js.map +1 -1
- package/dist/combinedSense.d.ts +128 -0
- package/dist/combinedSense.d.ts.map +1 -0
- package/dist/combinedSense.js +261 -0
- package/dist/combinedSense.js.map +1 -0
- package/dist/config.d.ts +351 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +99 -2
- package/dist/config.js.map +1 -1
- package/dist/connectorCadence.d.ts +135 -0
- package/dist/connectorCadence.d.ts.map +1 -0
- package/dist/connectorCadence.js +151 -0
- package/dist/connectorCadence.js.map +1 -0
- package/dist/dataDir.d.ts +33 -0
- package/dist/dataDir.d.ts.map +1 -0
- package/dist/dataDir.js +76 -0
- package/dist/dataDir.js.map +1 -0
- package/dist/deployedCadence.d.ts +167 -0
- package/dist/deployedCadence.d.ts.map +1 -0
- package/dist/deployedCadence.js +209 -0
- package/dist/deployedCadence.js.map +1 -0
- package/dist/deployedCadenceSource.d.ts +48 -0
- package/dist/deployedCadenceSource.d.ts.map +1 -0
- package/dist/deployedCadenceSource.js +74 -0
- package/dist/deployedCadenceSource.js.map +1 -0
- package/dist/diagnose.d.ts +1 -1
- package/dist/diagnose.d.ts.map +1 -1
- package/dist/diagnose.js +13 -12
- package/dist/diagnose.js.map +1 -1
- package/dist/discovery.d.ts +131 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +499 -0
- package/dist/discovery.js.map +1 -0
- package/dist/gemmaModelOta.d.ts +118 -0
- package/dist/gemmaModelOta.d.ts.map +1 -0
- package/dist/gemmaModelOta.js +291 -0
- package/dist/gemmaModelOta.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +41 -18
- package/dist/init.js.map +1 -1
- package/dist/keepAwake.d.ts +33 -0
- package/dist/keepAwake.d.ts.map +1 -0
- package/dist/keepAwake.js +54 -0
- package/dist/keepAwake.js.map +1 -0
- package/dist/legacyRecorderProfile.d.ts +20 -0
- package/dist/legacyRecorderProfile.d.ts.map +1 -0
- package/dist/legacyRecorderProfile.js +27 -0
- package/dist/legacyRecorderProfile.js.map +1 -0
- package/dist/localInferenceSchema.d.ts +132 -0
- package/dist/localInferenceSchema.d.ts.map +1 -0
- package/dist/localInferenceSchema.js +274 -0
- package/dist/localInferenceSchema.js.map +1 -0
- package/dist/localSenseE2eHarness.d.ts +101 -0
- package/dist/localSenseE2eHarness.d.ts.map +1 -0
- package/dist/localSenseE2eHarness.js +279 -0
- package/dist/localSenseE2eHarness.js.map +1 -0
- package/dist/localSensing.d.ts +268 -0
- package/dist/localSensing.d.ts.map +1 -0
- package/dist/localSensing.js +492 -0
- package/dist/localSensing.js.map +1 -0
- package/dist/modelIntegrity.d.ts +59 -0
- package/dist/modelIntegrity.d.ts.map +1 -0
- package/dist/modelIntegrity.js +165 -0
- package/dist/modelIntegrity.js.map +1 -0
- package/dist/pairVerify.d.ts +100 -0
- package/dist/pairVerify.d.ts.map +1 -0
- package/dist/pairVerify.js +296 -0
- package/dist/pairVerify.js.map +1 -0
- package/dist/qrScanner.d.ts +33 -0
- package/dist/qrScanner.d.ts.map +1 -0
- package/dist/qrScanner.js +103 -0
- package/dist/qrScanner.js.map +1 -0
- package/dist/runtimePaths.d.ts +10 -0
- package/dist/runtimePaths.d.ts.map +1 -0
- package/dist/runtimePaths.js +79 -0
- package/dist/runtimePaths.js.map +1 -0
- package/dist/sensingRunMode.d.ts +68 -0
- package/dist/sensingRunMode.d.ts.map +1 -0
- package/dist/sensingRunMode.js +346 -0
- package/dist/sensingRunMode.js.map +1 -0
- package/dist/setupMode.d.ts +98 -0
- package/dist/setupMode.d.ts.map +1 -0
- package/dist/setupMode.js +828 -0
- package/dist/setupMode.js.map +1 -0
- package/dist/setupProgress.d.ts +31 -0
- package/dist/setupProgress.d.ts.map +1 -0
- package/dist/setupProgress.js +173 -0
- package/dist/setupProgress.js.map +1 -0
- package/package.json +13 -4
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
#import <Cocoa/Cocoa.h>
|
|
2
|
+
#import <errno.h>
|
|
3
|
+
#import <fcntl.h>
|
|
4
|
+
#import <signal.h>
|
|
5
|
+
#import <spawn.h>
|
|
6
|
+
#import <stdlib.h>
|
|
7
|
+
#import <string.h>
|
|
8
|
+
#import <sys/file.h>
|
|
9
|
+
#import <sys/stat.h>
|
|
10
|
+
#import <sys/wait.h>
|
|
11
|
+
#import <unistd.h>
|
|
12
|
+
|
|
13
|
+
extern char **environ;
|
|
14
|
+
static int setupInstanceLockFd = -1;
|
|
15
|
+
static NSString *const TrillboardsSetupActivateNotification = @"com.trillboards.cctv-setup.activate";
|
|
16
|
+
|
|
17
|
+
static void normalizeTemporaryDirectory(void) {
|
|
18
|
+
const char *temporaryDirectory = getenv("TMPDIR");
|
|
19
|
+
if (temporaryDirectory == NULL || temporaryDirectory[0] == '\0') return;
|
|
20
|
+
|
|
21
|
+
struct stat info;
|
|
22
|
+
BOOL installerSandbox = strstr(temporaryDirectory, "PKInstallSandbox") != NULL;
|
|
23
|
+
BOOL usable = !installerSandbox &&
|
|
24
|
+
stat(temporaryDirectory, &info) == 0 &&
|
|
25
|
+
S_ISDIR(info.st_mode) &&
|
|
26
|
+
access(temporaryDirectory, W_OK | X_OK) == 0;
|
|
27
|
+
if (!usable) setenv("TMPDIR", "/private/tmp", 1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
static BOOL acquireSetupInstanceLock(void) {
|
|
31
|
+
NSString *lockPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"com.trillboards.cctv-setup.lock"];
|
|
32
|
+
setupInstanceLockFd = open(lockPath.fileSystemRepresentation, O_RDWR | O_CREAT, 0600);
|
|
33
|
+
if (setupInstanceLockFd == -1) return YES;
|
|
34
|
+
if (flock(setupInstanceLockFd, LOCK_EX | LOCK_NB) == 0) return YES;
|
|
35
|
+
close(setupInstanceLockFd);
|
|
36
|
+
setupInstanceLockFd = -1;
|
|
37
|
+
return NO;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@interface TrillboardsSetupDelegate : NSObject <NSApplicationDelegate>
|
|
41
|
+
@property(nonatomic, strong) NSWindow *window;
|
|
42
|
+
@property(nonatomic, strong) NSTextField *headline;
|
|
43
|
+
@property(nonatomic, strong) NSTextField *detail;
|
|
44
|
+
@property(nonatomic, strong) NSTextField *statusValue;
|
|
45
|
+
@property(nonatomic, strong) NSTextField *metric;
|
|
46
|
+
@property(nonatomic, strong) NSProgressIndicator *progress;
|
|
47
|
+
@property(nonatomic, strong) NSButton *actionButton;
|
|
48
|
+
@property(nonatomic, strong) NSArray<NSTextField *> *stepLabels;
|
|
49
|
+
@property(nonatomic, strong) NSTimer *timer;
|
|
50
|
+
@property(nonatomic, assign) pid_t pairProcessIdentifier;
|
|
51
|
+
@property(nonatomic, strong) dispatch_source_t pairProcessSource;
|
|
52
|
+
@property(nonatomic, copy) NSString *lastStage;
|
|
53
|
+
@property(nonatomic, copy) NSString *portalURL;
|
|
54
|
+
@property(nonatomic, assign) BOOL setupStarted;
|
|
55
|
+
@property(nonatomic, assign) BOOL terminating;
|
|
56
|
+
@end
|
|
57
|
+
|
|
58
|
+
@implementation TrillboardsSetupDelegate
|
|
59
|
+
|
|
60
|
+
- (NSString *)homePath:(NSString *)relative {
|
|
61
|
+
return [NSHomeDirectory() stringByAppendingPathComponent:relative];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
- (NSTextField *)label:(NSString *)text size:(CGFloat)size weight:(NSFontWeight)weight frame:(NSRect)frame {
|
|
65
|
+
NSTextField *field = [NSTextField labelWithString:text];
|
|
66
|
+
field.frame = frame;
|
|
67
|
+
field.font = [NSFont systemFontOfSize:size weight:weight];
|
|
68
|
+
field.lineBreakMode = NSLineBreakByWordWrapping;
|
|
69
|
+
field.maximumNumberOfLines = 0;
|
|
70
|
+
return field;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
|
|
74
|
+
(void)notification;
|
|
75
|
+
self.lastStage = @"welcome";
|
|
76
|
+
self.portalURL = @"https://trillboards.com/earner/screens";
|
|
77
|
+
self.setupStarted = NO;
|
|
78
|
+
[NSDistributedNotificationCenter.defaultCenter addObserver:self
|
|
79
|
+
selector:@selector(activateSetupWindow:)
|
|
80
|
+
name:TrillboardsSetupActivateNotification
|
|
81
|
+
object:nil];
|
|
82
|
+
[self buildWindow];
|
|
83
|
+
[self showWelcome];
|
|
84
|
+
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.45
|
|
85
|
+
target:self
|
|
86
|
+
selector:@selector(readProgress)
|
|
87
|
+
userInfo:nil
|
|
88
|
+
repeats:YES];
|
|
89
|
+
[self.window makeKeyAndOrderFront:nil];
|
|
90
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
- (void)activateSetupWindow:(NSNotification *)notification {
|
|
94
|
+
(void)notification;
|
|
95
|
+
[self.window makeKeyAndOrderFront:nil];
|
|
96
|
+
[NSApp activateIgnoringOtherApps:YES];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
|
|
100
|
+
(void)sender;
|
|
101
|
+
return YES;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
- (void)applicationWillTerminate:(NSNotification *)notification {
|
|
105
|
+
(void)notification;
|
|
106
|
+
self.terminating = YES;
|
|
107
|
+
[NSDistributedNotificationCenter.defaultCenter removeObserver:self];
|
|
108
|
+
[self.timer invalidate];
|
|
109
|
+
[self stopPairProcess];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
- (void)stopPairProcess {
|
|
113
|
+
pid_t pid = self.pairProcessIdentifier;
|
|
114
|
+
self.pairProcessIdentifier = 0;
|
|
115
|
+
if (self.pairProcessSource) {
|
|
116
|
+
dispatch_source_cancel(self.pairProcessSource);
|
|
117
|
+
self.pairProcessSource = nil;
|
|
118
|
+
}
|
|
119
|
+
if (pid <= 0) return;
|
|
120
|
+
|
|
121
|
+
kill(-pid, SIGTERM);
|
|
122
|
+
BOOL running = YES;
|
|
123
|
+
for (NSUInteger attempt = 0; attempt < 20 && running; attempt++) {
|
|
124
|
+
errno = 0;
|
|
125
|
+
running = kill(pid, 0) == 0 || errno != ESRCH;
|
|
126
|
+
if (!running) break;
|
|
127
|
+
[NSThread sleepForTimeInterval:0.05];
|
|
128
|
+
}
|
|
129
|
+
if (running) kill(-pid, SIGKILL);
|
|
130
|
+
|
|
131
|
+
int status = 0;
|
|
132
|
+
while (waitpid(pid, &status, 0) == -1 && errno == EINTR) {
|
|
133
|
+
// Retry an interrupted reap so the wrapper cannot remain a zombie.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
- (BOOL)launchSetupExecutable:(NSString *)executable
|
|
138
|
+
arguments:(NSArray<NSString *> *)arguments
|
|
139
|
+
logPath:(NSString *)logPath
|
|
140
|
+
error:(NSError **)error {
|
|
141
|
+
int logFd = open(logPath.fileSystemRepresentation, O_WRONLY | O_APPEND | O_CREAT, 0600);
|
|
142
|
+
if (logFd == -1) {
|
|
143
|
+
if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
|
|
144
|
+
return NO;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
posix_spawn_file_actions_t fileActions;
|
|
148
|
+
posix_spawnattr_t attributes;
|
|
149
|
+
posix_spawn_file_actions_init(&fileActions);
|
|
150
|
+
posix_spawn_file_actions_adddup2(&fileActions, logFd, STDOUT_FILENO);
|
|
151
|
+
posix_spawn_file_actions_adddup2(&fileActions, logFd, STDERR_FILENO);
|
|
152
|
+
posix_spawn_file_actions_addclose(&fileActions, logFd);
|
|
153
|
+
posix_spawnattr_init(&attributes);
|
|
154
|
+
posix_spawnattr_setflags(&attributes, POSIX_SPAWN_SETPGROUP);
|
|
155
|
+
posix_spawnattr_setpgroup(&attributes, 0);
|
|
156
|
+
|
|
157
|
+
NSUInteger argumentCount = arguments.count + 2;
|
|
158
|
+
char **argv = calloc(argumentCount, sizeof(char *));
|
|
159
|
+
argv[0] = strdup(executable.fileSystemRepresentation);
|
|
160
|
+
for (NSUInteger index = 0; index < arguments.count; index++) {
|
|
161
|
+
argv[index + 1] = strdup(arguments[index].UTF8String);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pid_t pid = 0;
|
|
165
|
+
int spawnError = posix_spawn(&pid,
|
|
166
|
+
executable.fileSystemRepresentation,
|
|
167
|
+
&fileActions,
|
|
168
|
+
&attributes,
|
|
169
|
+
argv,
|
|
170
|
+
environ);
|
|
171
|
+
for (NSUInteger index = 0; index < argumentCount; index++) free(argv[index]);
|
|
172
|
+
free(argv);
|
|
173
|
+
posix_spawnattr_destroy(&attributes);
|
|
174
|
+
posix_spawn_file_actions_destroy(&fileActions);
|
|
175
|
+
close(logFd);
|
|
176
|
+
if (spawnError != 0) {
|
|
177
|
+
if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:spawnError userInfo:nil];
|
|
178
|
+
return NO;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
self.pairProcessIdentifier = pid;
|
|
182
|
+
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,
|
|
183
|
+
(uintptr_t)pid,
|
|
184
|
+
DISPATCH_PROC_EXIT,
|
|
185
|
+
dispatch_get_main_queue());
|
|
186
|
+
self.pairProcessSource = source;
|
|
187
|
+
__weak typeof(self) weakSelf = self;
|
|
188
|
+
dispatch_source_set_event_handler(source, ^{
|
|
189
|
+
[weakSelf pairProcessDidExit:pid];
|
|
190
|
+
});
|
|
191
|
+
dispatch_resume(source);
|
|
192
|
+
return YES;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
- (void)pairProcessDidExit:(pid_t)pid {
|
|
196
|
+
if (self.pairProcessIdentifier != pid) return;
|
|
197
|
+
int status = 0;
|
|
198
|
+
pid_t waited = waitpid(pid, &status, 0);
|
|
199
|
+
self.pairProcessIdentifier = 0;
|
|
200
|
+
if (self.pairProcessSource) {
|
|
201
|
+
dispatch_source_cancel(self.pairProcessSource);
|
|
202
|
+
self.pairProcessSource = nil;
|
|
203
|
+
}
|
|
204
|
+
BOOL failed = waited == -1 || !WIFEXITED(status) || WEXITSTATUS(status) != 0;
|
|
205
|
+
NSInteger exitCode = waited != -1 && WIFEXITED(status) ? WEXITSTATUS(status) : -1;
|
|
206
|
+
if (!failed || self.terminating || [self.lastStage isEqualToString:@"ready"] || [self.lastStage isEqualToString:@"error"] || [self.lastStage isEqualToString:@"local_ai_error"]) return;
|
|
207
|
+
__weak typeof(self) weakSelf = self;
|
|
208
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.6 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
209
|
+
TrillboardsSetupDelegate *strongSelf = weakSelf;
|
|
210
|
+
if (!strongSelf || strongSelf.terminating || [strongSelf.lastStage isEqualToString:@"ready"] || [strongSelf.lastStage isEqualToString:@"error"] || [strongSelf.lastStage isEqualToString:@"local_ai_error"]) return;
|
|
211
|
+
NSString *message = exitCode >= 0
|
|
212
|
+
? [NSString stringWithFormat:@"The camera setup helper exited with code %ld. Try again; if it repeats, send setup-app.log to Trillboards support.", (long)exitCode]
|
|
213
|
+
: @"The camera setup helper stopped unexpectedly. Try again; if it repeats, send setup-app.log to Trillboards support.";
|
|
214
|
+
[strongSelf showLocalError:@"Setup stopped before finishing" message:message];
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
- (void)buildWindow {
|
|
219
|
+
self.window = [[NSWindow alloc]
|
|
220
|
+
initWithContentRect:NSMakeRect(0, 0, 760, 540)
|
|
221
|
+
styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable
|
|
222
|
+
backing:NSBackingStoreBuffered
|
|
223
|
+
defer:NO];
|
|
224
|
+
self.window.title = @"Trillboards CCTV Setup";
|
|
225
|
+
self.window.minSize = NSMakeSize(760, 540);
|
|
226
|
+
self.window.maxSize = NSMakeSize(760, 540);
|
|
227
|
+
[self.window center];
|
|
228
|
+
|
|
229
|
+
NSView *root = self.window.contentView;
|
|
230
|
+
NSView *sidebar = [[NSView alloc] initWithFrame:NSMakeRect(0, 0, 190, 540)];
|
|
231
|
+
sidebar.wantsLayer = YES;
|
|
232
|
+
sidebar.layer.backgroundColor = NSColor.controlBackgroundColor.CGColor;
|
|
233
|
+
[root addSubview:sidebar];
|
|
234
|
+
|
|
235
|
+
NSTextField *brand = [self label:@"Trillboards" size:16 weight:NSFontWeightSemibold frame:NSMakeRect(24, 480, 145, 28)];
|
|
236
|
+
[sidebar addSubview:brand];
|
|
237
|
+
|
|
238
|
+
NSArray<NSString *> *names = @[@"Welcome", @"Account", @"Recorder", @"Cameras", @"Local AI", @"Ready"];
|
|
239
|
+
NSMutableArray<NSTextField *> *labels = [NSMutableArray array];
|
|
240
|
+
for (NSUInteger index = 0; index < names.count; index++) {
|
|
241
|
+
NSTextField *step = [self label:[NSString stringWithFormat:@"%lu %@", index + 1, names[index]]
|
|
242
|
+
size:13
|
|
243
|
+
weight:NSFontWeightRegular
|
|
244
|
+
frame:NSMakeRect(24, 420 - (index * 48), 150, 28)];
|
|
245
|
+
step.textColor = NSColor.secondaryLabelColor;
|
|
246
|
+
[sidebar addSubview:step];
|
|
247
|
+
[labels addObject:step];
|
|
248
|
+
}
|
|
249
|
+
self.stepLabels = labels;
|
|
250
|
+
|
|
251
|
+
NSTextField *eyebrow = [self label:@"CUSTOMER SETUP" size:12 weight:NSFontWeightMedium frame:NSMakeRect(236, 466, 465, 22)];
|
|
252
|
+
eyebrow.textColor = [NSColor colorWithCalibratedRed:0 green:0.43 blue:0.61 alpha:1];
|
|
253
|
+
[root addSubview:eyebrow];
|
|
254
|
+
|
|
255
|
+
self.headline = [self label:@"" size:30 weight:NSFontWeightSemibold frame:NSMakeRect(236, 385, 465, 76)];
|
|
256
|
+
[root addSubview:self.headline];
|
|
257
|
+
self.detail = [self label:@"" size:16 weight:NSFontWeightRegular frame:NSMakeRect(236, 303, 465, 76)];
|
|
258
|
+
self.detail.textColor = NSColor.secondaryLabelColor;
|
|
259
|
+
[root addSubview:self.detail];
|
|
260
|
+
|
|
261
|
+
self.statusValue = [self label:@"" size:25 weight:NSFontWeightSemibold frame:NSMakeRect(236, 218, 465, 62)];
|
|
262
|
+
self.statusValue.alignment = NSTextAlignmentCenter;
|
|
263
|
+
self.statusValue.font = [NSFont monospacedSystemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
264
|
+
self.statusValue.wantsLayer = YES;
|
|
265
|
+
self.statusValue.layer.backgroundColor = NSColor.controlBackgroundColor.CGColor;
|
|
266
|
+
self.statusValue.layer.borderColor = NSColor.separatorColor.CGColor;
|
|
267
|
+
self.statusValue.layer.borderWidth = 1;
|
|
268
|
+
self.statusValue.layer.cornerRadius = 6;
|
|
269
|
+
[root addSubview:self.statusValue];
|
|
270
|
+
|
|
271
|
+
self.metric = [self label:@"" size:13 weight:NSFontWeightRegular frame:NSMakeRect(236, 174, 465, 28)];
|
|
272
|
+
self.metric.textColor = NSColor.secondaryLabelColor;
|
|
273
|
+
[root addSubview:self.metric];
|
|
274
|
+
|
|
275
|
+
self.progress = [[NSProgressIndicator alloc] initWithFrame:NSMakeRect(236, 151, 465, 10)];
|
|
276
|
+
self.progress.style = NSProgressIndicatorStyleBar;
|
|
277
|
+
self.progress.indeterminate = NO;
|
|
278
|
+
self.progress.minValue = 0;
|
|
279
|
+
self.progress.maxValue = 100;
|
|
280
|
+
[root addSubview:self.progress];
|
|
281
|
+
|
|
282
|
+
NSTextField *privacy = [self label:@"Camera credentials stay on this computer. Cloud inference is off by default."
|
|
283
|
+
size:12
|
|
284
|
+
weight:NSFontWeightRegular
|
|
285
|
+
frame:NSMakeRect(236, 34, 300, 42)];
|
|
286
|
+
privacy.textColor = NSColor.secondaryLabelColor;
|
|
287
|
+
[root addSubview:privacy];
|
|
288
|
+
|
|
289
|
+
self.actionButton = [[NSButton alloc] initWithFrame:NSMakeRect(562, 36, 139, 40)];
|
|
290
|
+
self.actionButton.title = @"Start setup";
|
|
291
|
+
self.actionButton.bezelStyle = NSBezelStyleRounded;
|
|
292
|
+
self.actionButton.target = self;
|
|
293
|
+
self.actionButton.action = @selector(actionPressed);
|
|
294
|
+
[root addSubview:self.actionButton];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
- (void)showWelcome {
|
|
298
|
+
self.lastStage = @"welcome";
|
|
299
|
+
self.headline.stringValue = @"Set up this computer for CCTV";
|
|
300
|
+
self.detail.stringValue = @"Connect your Trillboards account, find your recorder, and verify every camera before screens are created.";
|
|
301
|
+
self.statusValue.stringValue = @"About 5 minutes";
|
|
302
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
303
|
+
self.metric.stringValue = @"Keep this computer and the recorder on the same network.";
|
|
304
|
+
self.progress.doubleValue = 0;
|
|
305
|
+
self.actionButton.title = @"Start setup";
|
|
306
|
+
self.actionButton.enabled = YES;
|
|
307
|
+
[self updateSteps:0];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
- (void)showLocalError:(NSString *)title message:(NSString *)message {
|
|
311
|
+
self.lastStage = @"error";
|
|
312
|
+
self.headline.stringValue = title;
|
|
313
|
+
self.detail.stringValue = message;
|
|
314
|
+
self.statusValue.stringValue = @"Setup stopped";
|
|
315
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
316
|
+
self.metric.stringValue = @"Details: Library/Logs/Trillboards/setup-app.log";
|
|
317
|
+
self.progress.doubleValue = 0;
|
|
318
|
+
self.actionButton.title = @"Try again";
|
|
319
|
+
self.actionButton.enabled = YES;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
- (void)actionPressed {
|
|
323
|
+
if ([self.lastStage isEqualToString:@"waiting_for_account"] ||
|
|
324
|
+
[self.lastStage isEqualToString:@"account_attention"]) {
|
|
325
|
+
[self openURL:self.portalURL];
|
|
326
|
+
} else if ([self.lastStage isEqualToString:@"ready"]) {
|
|
327
|
+
[self openURL:@"https://trillboards.com/earner/screens"];
|
|
328
|
+
} else if ([self.lastStage isEqualToString:@"local_ai_error"]) {
|
|
329
|
+
[NSApp terminate:nil];
|
|
330
|
+
} else if ([self.lastStage isEqualToString:@"welcome"] || [self.lastStage isEqualToString:@"error"]) {
|
|
331
|
+
[self startSetup];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
- (void)startSetup {
|
|
336
|
+
NSString *pairer = [self homePath:@"Library/Trillboards/edge/bin/trillboards-pair"];
|
|
337
|
+
NSString *stateDirectory = [self homePath:@"Library/Application Support/Trillboards"];
|
|
338
|
+
NSString *statePath = [stateDirectory stringByAppendingPathComponent:@"setup-progress.json"];
|
|
339
|
+
NSString *logDirectory = [self homePath:@"Library/Logs/Trillboards"];
|
|
340
|
+
NSString *logPath = [logDirectory stringByAppendingPathComponent:@"setup-app.log"];
|
|
341
|
+
NSFileManager *files = NSFileManager.defaultManager;
|
|
342
|
+
if (![files isExecutableFileAtPath:pairer]) {
|
|
343
|
+
[self showLocalError:@"Setup app is incomplete" message:@"Reinstall the Trillboards CCTV connector, then try again."];
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
[files createDirectoryAtPath:stateDirectory withIntermediateDirectories:YES attributes:nil error:nil];
|
|
347
|
+
[files removeItemAtPath:statePath error:nil];
|
|
348
|
+
[files createDirectoryAtPath:logDirectory withIntermediateDirectories:YES attributes:nil error:nil];
|
|
349
|
+
if (![files fileExistsAtPath:logPath]) [files createFileAtPath:logPath contents:nil attributes:nil];
|
|
350
|
+
|
|
351
|
+
self.lastStage = @"starting";
|
|
352
|
+
self.setupStarted = YES;
|
|
353
|
+
self.headline.stringValue = @"Starting camera setup";
|
|
354
|
+
self.detail.stringValue = @"Connecting securely to Trillboards.";
|
|
355
|
+
self.statusValue.stringValue = @"Please wait";
|
|
356
|
+
self.metric.stringValue = @"No Terminal or command window is required.";
|
|
357
|
+
self.progress.doubleValue = 8;
|
|
358
|
+
self.actionButton.enabled = NO;
|
|
359
|
+
|
|
360
|
+
NSError *error = nil;
|
|
361
|
+
if (![self launchSetupExecutable:pairer
|
|
362
|
+
arguments:@[@"--setup", @"--status-file", statePath]
|
|
363
|
+
logPath:logPath
|
|
364
|
+
error:&error]) {
|
|
365
|
+
[self showLocalError:@"Could not start setup" message:@"Reinstall the Trillboards CCTV connector, then try again."];
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
- (void)readProgress {
|
|
371
|
+
NSString *statePath = [self homePath:@"Library/Application Support/Trillboards/setup-progress.json"];
|
|
372
|
+
if (!self.setupStarted) return;
|
|
373
|
+
NSData *data = [NSData dataWithContentsOfFile:statePath];
|
|
374
|
+
if (!data) return;
|
|
375
|
+
NSDictionary *state = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
376
|
+
if (![state isKindOfClass:NSDictionary.class]) return;
|
|
377
|
+
[self renderState:state];
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
- (void)renderState:(NSDictionary *)state {
|
|
381
|
+
NSString *stage = [state[@"stage"] isKindOfClass:NSString.class] ? state[@"stage"] : nil;
|
|
382
|
+
if (!stage) return;
|
|
383
|
+
self.lastStage = stage;
|
|
384
|
+
if ([state[@"portalUrl"] isKindOfClass:NSString.class]) self.portalURL = state[@"portalUrl"];
|
|
385
|
+
if ([state[@"title"] isKindOfClass:NSString.class]) self.headline.stringValue = state[@"title"];
|
|
386
|
+
if ([state[@"message"] isKindOfClass:NSString.class]) self.detail.stringValue = state[@"message"];
|
|
387
|
+
|
|
388
|
+
NSNumber *cameraCount = state[@"cameraCount"];
|
|
389
|
+
NSNumber *validated = state[@"validatedFeeds"];
|
|
390
|
+
NSNumber *devices = state[@"devicesFound"];
|
|
391
|
+
NSNumber *rejected = state[@"rejectedFeeds"];
|
|
392
|
+
if (([stage isEqualToString:@"waiting_for_account"] ||
|
|
393
|
+
[stage isEqualToString:@"account_attention"]) &&
|
|
394
|
+
[state[@"displayCode"] isKindOfClass:NSString.class]) {
|
|
395
|
+
self.statusValue.font = [NSFont monospacedSystemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
396
|
+
self.statusValue.stringValue = state[@"displayCode"];
|
|
397
|
+
} else if (cameraCount.integerValue > 0) {
|
|
398
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
399
|
+
self.statusValue.stringValue = [NSString stringWithFormat:@"%ld camera%@", cameraCount.integerValue, cameraCount.integerValue == 1 ? @"" : @"s"];
|
|
400
|
+
} else if (validated.integerValue > 0) {
|
|
401
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
402
|
+
self.statusValue.stringValue = [NSString stringWithFormat:@"%ld feed%@ ready", validated.integerValue, validated.integerValue == 1 ? @"" : @"s"];
|
|
403
|
+
} else if (devices != nil) {
|
|
404
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
405
|
+
self.statusValue.stringValue = [NSString stringWithFormat:@"%ld device%@ found", devices.integerValue, devices.integerValue == 1 ? @"" : @"s"];
|
|
406
|
+
} else if ([stage isEqualToString:@"ready"]) {
|
|
407
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
408
|
+
self.statusValue.stringValue = @"Setup complete";
|
|
409
|
+
} else if ([stage isEqualToString:@"local_ai_error"]) {
|
|
410
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
411
|
+
self.statusValue.stringValue = @"Local AI needs attention";
|
|
412
|
+
} else if ([stage isEqualToString:@"error"]) {
|
|
413
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
414
|
+
self.statusValue.stringValue = @"Setup stopped";
|
|
415
|
+
} else {
|
|
416
|
+
self.statusValue.font = [NSFont systemFontOfSize:25 weight:NSFontWeightSemibold];
|
|
417
|
+
self.statusValue.stringValue = @"In progress";
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (rejected.integerValue > 0) {
|
|
421
|
+
self.metric.stringValue = [NSString stringWithFormat:@"%ld black or unavailable input%@ ignored.", rejected.integerValue, rejected.integerValue == 1 ? @" was" : @"s were"];
|
|
422
|
+
} else if ([stage isEqualToString:@"account_attention"]) {
|
|
423
|
+
self.metric.stringValue = @"No cameras were added. It is safe to switch accounts.";
|
|
424
|
+
} else if ([stage isEqualToString:@"local_ai"]) {
|
|
425
|
+
self.metric.stringValue = @"Checking the private model installed with Trillboards.";
|
|
426
|
+
} else if ([stage isEqualToString:@"local_ai_error"]) {
|
|
427
|
+
self.metric.stringValue = @"Reinstall Trillboards CCTV Setup; your camera pairing is saved.";
|
|
428
|
+
} else if ([stage isEqualToString:@"ready"]) {
|
|
429
|
+
self.metric.stringValue = @"Trillboards starts automatically when this computer logs in.";
|
|
430
|
+
} else if ([stage isEqualToString:@"error"]) {
|
|
431
|
+
self.metric.stringValue = @"Details: Library/Logs/Trillboards/setup-app.log";
|
|
432
|
+
} else {
|
|
433
|
+
self.metric.stringValue = @"Camera credentials stay encrypted on this computer.";
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
self.progress.doubleValue = [self progressForStage:stage];
|
|
437
|
+
[self updateSteps:[self stepForStage:stage]];
|
|
438
|
+
if ([stage isEqualToString:@"waiting_for_account"]) {
|
|
439
|
+
self.actionButton.title = @"Open Trillboards";
|
|
440
|
+
self.actionButton.enabled = YES;
|
|
441
|
+
} else if ([stage isEqualToString:@"account_attention"]) {
|
|
442
|
+
self.actionButton.title = @"Switch account";
|
|
443
|
+
self.actionButton.enabled = YES;
|
|
444
|
+
} else if ([stage isEqualToString:@"local_ai_error"]) {
|
|
445
|
+
self.actionButton.title = @"Close";
|
|
446
|
+
self.actionButton.enabled = YES;
|
|
447
|
+
} else if ([stage isEqualToString:@"ready"]) {
|
|
448
|
+
self.actionButton.title = @"View cameras";
|
|
449
|
+
self.actionButton.enabled = YES;
|
|
450
|
+
} else if ([stage isEqualToString:@"error"]) {
|
|
451
|
+
self.actionButton.title = @"Try again";
|
|
452
|
+
self.actionButton.enabled = YES;
|
|
453
|
+
} else {
|
|
454
|
+
self.actionButton.title = @"Working...";
|
|
455
|
+
self.actionButton.enabled = NO;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
- (NSInteger)stepForStage:(NSString *)stage {
|
|
460
|
+
if ([@[@"waiting_for_account", @"account_attention", @"account_connected"] containsObject:stage]) return 1;
|
|
461
|
+
if ([@[@"discovering", @"devices_found"] containsObject:stage]) return 2;
|
|
462
|
+
if ([@[@"validating", @"cameras_created"] containsObject:stage]) return 3;
|
|
463
|
+
if ([@[@"local_ai", @"starting_service"] containsObject:stage]) return 4;
|
|
464
|
+
if ([stage isEqualToString:@"ready"]) return 5;
|
|
465
|
+
return 0;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
- (double)progressForStage:(NSString *)stage {
|
|
469
|
+
NSDictionary<NSString *, NSNumber *> *values = @{
|
|
470
|
+
@"waiting_for_account": @18, @"account_attention": @18, @"account_connected": @30, @"discovering": @42,
|
|
471
|
+
@"devices_found": @50, @"validating": @68, @"cameras_created": @80,
|
|
472
|
+
@"local_ai": @88, @"starting_service": @94, @"ready": @100
|
|
473
|
+
};
|
|
474
|
+
return values[stage] ? values[stage].doubleValue : 8;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
- (void)updateSteps:(NSInteger)active {
|
|
478
|
+
NSArray<NSString *> *names = @[@"Welcome", @"Account", @"Recorder", @"Cameras", @"Local AI", @"Ready"];
|
|
479
|
+
for (NSInteger index = 0; index < (NSInteger)self.stepLabels.count; index++) {
|
|
480
|
+
NSTextField *field = self.stepLabels[(NSUInteger)index];
|
|
481
|
+
NSString *prefix = index < active ? @"OK" : [NSString stringWithFormat:@"%ld", index + 1];
|
|
482
|
+
field.stringValue = [NSString stringWithFormat:@"%@ %@", prefix, names[(NSUInteger)index]];
|
|
483
|
+
field.font = [NSFont systemFontOfSize:13 weight:index == active ? NSFontWeightSemibold : NSFontWeightRegular];
|
|
484
|
+
field.textColor = index <= active ? NSColor.labelColor : NSColor.secondaryLabelColor;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
- (void)openURL:(NSString *)string {
|
|
489
|
+
NSURL *url = [NSURL URLWithString:string];
|
|
490
|
+
if (url) [NSWorkspace.sharedWorkspace openURL:url];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
@end
|
|
494
|
+
|
|
495
|
+
static int runCancellationSmokeTest(void) {
|
|
496
|
+
NSString *pidPath = [NSTemporaryDirectory()
|
|
497
|
+
stringByAppendingPathComponent:[NSString stringWithFormat:@"trillboards-setup-child-%@.pid", NSUUID.UUID.UUIDString]];
|
|
498
|
+
NSArray<NSString *> *arguments = @[
|
|
499
|
+
@"-c",
|
|
500
|
+
@"sleep 120 & child=$!; printf '%s\\n' \"$child\" > \"$1\"; wait \"$child\"",
|
|
501
|
+
@"trillboards-setup-cancel-smoke",
|
|
502
|
+
pidPath
|
|
503
|
+
];
|
|
504
|
+
TrillboardsSetupDelegate *delegate = [[TrillboardsSetupDelegate alloc] init];
|
|
505
|
+
NSError *launchError = nil;
|
|
506
|
+
if (![delegate launchSetupExecutable:@"/bin/bash"
|
|
507
|
+
arguments:arguments
|
|
508
|
+
logPath:@"/dev/null"
|
|
509
|
+
error:&launchError]) {
|
|
510
|
+
fprintf(stderr, "Could not launch cancellation smoke process: %s\n", launchError.localizedDescription.UTF8String);
|
|
511
|
+
return 1;
|
|
512
|
+
}
|
|
513
|
+
pid_t wrapperPid = delegate.pairProcessIdentifier;
|
|
514
|
+
|
|
515
|
+
pid_t childPid = 0;
|
|
516
|
+
for (NSUInteger attempt = 0; attempt < 40 && childPid <= 0; attempt++) {
|
|
517
|
+
NSString *rawPid = [NSString stringWithContentsOfFile:pidPath encoding:NSUTF8StringEncoding error:nil];
|
|
518
|
+
childPid = (pid_t)rawPid.integerValue;
|
|
519
|
+
if (childPid <= 0) [NSThread sleepForTimeInterval:0.05];
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
[delegate stopPairProcess];
|
|
523
|
+
|
|
524
|
+
BOOL wrapperStopped = NO;
|
|
525
|
+
BOOL childStopped = NO;
|
|
526
|
+
for (NSUInteger attempt = 0; attempt < 40; attempt++) {
|
|
527
|
+
errno = 0;
|
|
528
|
+
wrapperStopped = kill(wrapperPid, 0) == -1 && errno == ESRCH;
|
|
529
|
+
errno = 0;
|
|
530
|
+
childStopped = childPid > 0 && kill(childPid, 0) == -1 && errno == ESRCH;
|
|
531
|
+
if (wrapperStopped && childStopped) break;
|
|
532
|
+
[NSThread sleepForTimeInterval:0.05];
|
|
533
|
+
}
|
|
534
|
+
[NSFileManager.defaultManager removeItemAtPath:pidPath error:nil];
|
|
535
|
+
if (childPid <= 0 || !wrapperStopped || !childStopped) {
|
|
536
|
+
fprintf(stderr, "Native setup cancellation smoke test left a process running.\n");
|
|
537
|
+
return 1;
|
|
538
|
+
}
|
|
539
|
+
fprintf(stdout, "Trillboards CCTV Setup cancellation smoke test passed.\n");
|
|
540
|
+
return 0;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
static int runTemporaryDirectorySmokeTest(void) {
|
|
544
|
+
const char *environmentTemporaryDirectory = getenv("TMPDIR");
|
|
545
|
+
if (environmentTemporaryDirectory != NULL &&
|
|
546
|
+
strstr(environmentTemporaryDirectory, "PKInstallSandbox") != NULL) {
|
|
547
|
+
fprintf(stderr, "Native setup retained PackageKit's temporary sandbox.\n");
|
|
548
|
+
return 1;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
NSString *temporaryDirectory = NSTemporaryDirectory();
|
|
552
|
+
BOOL isDirectory = NO;
|
|
553
|
+
if (![NSFileManager.defaultManager fileExistsAtPath:temporaryDirectory isDirectory:&isDirectory] ||
|
|
554
|
+
!isDirectory ||
|
|
555
|
+
![NSFileManager.defaultManager isWritableFileAtPath:temporaryDirectory]) {
|
|
556
|
+
fprintf(stderr, "Native setup temporary directory is unavailable: %s\n", temporaryDirectory.UTF8String);
|
|
557
|
+
return 1;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
NSString *templatePath = [temporaryDirectory stringByAppendingPathComponent:@"trillboards-setup-tmp.XXXXXX"];
|
|
561
|
+
char *template = strdup(templatePath.fileSystemRepresentation);
|
|
562
|
+
if (template == NULL) {
|
|
563
|
+
fprintf(stderr, "Native setup could not allocate a temporary-directory template.\n");
|
|
564
|
+
return 1;
|
|
565
|
+
}
|
|
566
|
+
char *created = mkdtemp(template);
|
|
567
|
+
if (created == NULL) {
|
|
568
|
+
fprintf(stderr, "Native setup could not create a temporary directory: %s\n", strerror(errno));
|
|
569
|
+
free(template);
|
|
570
|
+
return 1;
|
|
571
|
+
}
|
|
572
|
+
rmdir(created);
|
|
573
|
+
free(template);
|
|
574
|
+
fprintf(stdout, "Trillboards CCTV Setup temporary-directory smoke test passed.\n");
|
|
575
|
+
return 0;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
int main(int argc, const char *argv[]) {
|
|
579
|
+
// PackageKit deletes its sandbox immediately after postinstall. Never let a
|
|
580
|
+
// setup app or its pairing child retain that now-stale TMPDIR.
|
|
581
|
+
normalizeTemporaryDirectory();
|
|
582
|
+
@autoreleasepool {
|
|
583
|
+
if (argc == 2 && [[NSString stringWithUTF8String:argv[1]] isEqualToString:@"--smoke-test-cancel"]) {
|
|
584
|
+
return runCancellationSmokeTest();
|
|
585
|
+
}
|
|
586
|
+
if (argc == 2 && [[NSString stringWithUTF8String:argv[1]] isEqualToString:@"--smoke-test-tempdir"]) {
|
|
587
|
+
return runTemporaryDirectorySmokeTest();
|
|
588
|
+
}
|
|
589
|
+
if (!acquireSetupInstanceLock()) {
|
|
590
|
+
[NSDistributedNotificationCenter.defaultCenter postNotificationName:TrillboardsSetupActivateNotification
|
|
591
|
+
object:nil
|
|
592
|
+
userInfo:nil
|
|
593
|
+
deliverImmediately:YES];
|
|
594
|
+
return 0;
|
|
595
|
+
}
|
|
596
|
+
NSApplication *application = NSApplication.sharedApplication;
|
|
597
|
+
TrillboardsSetupDelegate *delegate = [[TrillboardsSetupDelegate alloc] init];
|
|
598
|
+
application.delegate = delegate;
|
|
599
|
+
[application setActivationPolicy:NSApplicationActivationPolicyRegular];
|
|
600
|
+
[application run];
|
|
601
|
+
}
|
|
602
|
+
return 0;
|
|
603
|
+
}
|