Skip to content

Commit

Permalink
Made the Rlogs script more "robus"
Browse files Browse the repository at this point in the history
  • Loading branch information
OrigamingWasTaken committed Oct 16, 2024
1 parent ed02732 commit ed9fee5
Show file tree
Hide file tree
Showing 2 changed files with 189 additions and 45 deletions.
38 changes: 38 additions & 0 deletions scripts/build/sidecar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Sidecar

These scripts are used for native operations and are invoked from neutralino. Most of them are made using assistance from AI due to the lack of native contributors (skill issue on my part).

## Bootstrap

Launches the `main` executable with these arguments:
```
--path=<app Resources folder>
--enable-extensions=true"
--window-enable-inspector=true
```

It also captures if the app was launched using a deeplink, in which case it will either, launch the app, or re-launch the main executable if it is already open. The bootstrap will exit once the main executable exits.

## Rlogs (Roblox Logs)

This is just a simple file watcher, that returns new changes (lines) to stdOut as arrays.

When *rlogs* needs to output a message, it will be prefixed with: `message:`. Every 5 minutes, it checks if the log file size changed, if it did, it should check for the new content it potentially missed, and unwatch + rewatch the file.

## UrlScheme

Utility CLI to change the default handler for an URI (LSHandler). Used to open AppleBlox from the website.

## Window Manager

Takes **stdIn** like this format:
```json
[{"appName": "Roblox", "x": 0, "y": 100, "w": 40, "h": 80}]
```
The array can contain any number of objects. The script will move the *first* window it finds of the specified app accordingly. In the frontend to be able to talk with this process easily, we create a pipe using `mkfifo` at `/tmp/window_relay_ablox`, then use the `echo` command to pass the stdIn. To be able to take input from the pipe, we start the window manager like this:

```bash
./window_manager <> pipe_path
```

Please note that the **window manager** currently only has 1 use case and that is the rythm game `Project: Afternight`. In the future we may use it more.
196 changes: 151 additions & 45 deletions scripts/build/sidecar/rlogs.m
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
#import <Foundation/Foundation.h>
#import <signal.h>

@interface FileWatcher : NSObject
@property (nonatomic, copy) NSString *filePath;
@property (nonatomic, strong) dispatch_source_t source;
@property (nonatomic, assign) int fileDescriptor;
@property (nonatomic, strong) NSFileHandle *fileHandle;
@property (nonatomic, assign) unsigned long long lastFileSize;
@property (nonatomic, strong) dispatch_queue_t queue;
@property (nonatomic, strong) dispatch_source_t timer;
@property (nonatomic, strong) dispatch_source_t parentCheckTimer;
@property (nonatomic, copy) NSString *outputFilePath;

- (instancetype)initWithFilePath:(NSString *)filePath;
- (void)startWatching;
Expand All @@ -19,118 +23,220 @@ - (instancetype)initWithFilePath:(NSString *)filePath {
if (self) {
_filePath = [filePath copy];
_lastFileSize = 0;
_queue = dispatch_queue_create("com.appleblox.filewatcher", DISPATCH_QUEUE_SERIAL);
[self setupOutputFile];
}
return self;
}

- (void)startWatching {
NSError *error = nil;
self.fileHandle = [NSFileHandle fileHandleForReadingFromURL:[NSURL fileURLWithPath:self.filePath] error:&error];
if (error) {
NSLog(@"Error opening file: %@", error);
return;
- (void)setupOutputFile {
NSString *tmpDir = NSTemporaryDirectory();
NSString *outputDir = [tmpDir stringByAppendingPathComponent:@"rlogs_ablox"];

NSFileManager *fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:outputDir]) {
NSError *error = nil;
[fileManager createDirectoryAtPath:outputDir withIntermediateDirectories:YES attributes:nil error:&error];
if (error) {
[self logMessage:[NSString stringWithFormat:@"Error creating output directory: %@", error]];
return;
}
}

self.outputFilePath = [outputDir stringByAppendingPathComponent:@"output.log"];
}

- (void)startWatching {
[self setupSignalHandler];
[self setupFileWatcher];
[self setupPeriodicCheck];
[self setupParentProcessCheck];
}

self.fileDescriptor = self.fileHandle.fileDescriptor;
- (void)setupSignalHandler {
signal(SIGHUP, SIG_IGN);
dispatch_source_t signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, dispatch_get_main_queue());
dispatch_source_set_event_handler(signalSource, ^{
[self logMessage:@"Received SIGHUP, exiting..."];
exit(0);
});
dispatch_resume(signalSource);
}

- (void)setupFileWatcher {
self.fileDescriptor = open([self.filePath UTF8String], O_RDONLY);
if (self.fileDescriptor < 0) {
NSLog(@"Invalid file descriptor");
[self logMessage:[NSString stringWithFormat:@"Error opening file: %s", strerror(errno)]];
return;
}

[self updateLastFileSize];

self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, self.fileDescriptor,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB | DISPATCH_VNODE_LINK | DISPATCH_VNODE_RENAME | DISPATCH_VNODE_REVOKE,
self.queue);

dispatch_source_set_event_handler(self.source, ^{
[self handleFileChange];
});

dispatch_source_set_cancel_handler(self.source, ^{
close(self.fileDescriptor);
});

dispatch_resume(self.source);
}

- (void)setupPeriodicCheck {
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
dispatch_source_set_timer(self.timer, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC), 5 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);

dispatch_source_set_event_handler(self.timer, ^{
[self performManualCheck];
});

dispatch_resume(self.timer);
}

- (void)setupParentProcessCheck {
self.parentCheckTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue);
dispatch_source_set_timer(self.parentCheckTimer, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC), 1 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);

dispatch_source_set_event_handler(self.parentCheckTimer, ^{
if (getppid() == 1) {
[self logMessage:@"Parent process disconnected, exiting..."];
exit(0);
}
});

dispatch_resume(self.parentCheckTimer);
}

- (void)performManualCheck {
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error];
if (error) {
[self logMessage:[NSString stringWithFormat:@"Error getting file attributes: %@", error]];
return;
}

unsigned long long currentFileSize = [attributes fileSize];
if (currentFileSize > self.lastFileSize) {
[self logMessage:@"Manual check detected new content. Rewatching file."];
[self stopWatching];
[self startWatching];
}
}

- (void)stopWatching {
if (self.source) {
dispatch_source_cancel(self.source);
self.source = NULL;
}
[self.fileHandle closeFile];
if (self.timer) {
dispatch_source_cancel(self.timer);
self.timer = NULL;
}
if (self.parentCheckTimer) {
dispatch_source_cancel(self.parentCheckTimer);
self.parentCheckTimer = NULL;
}
}

- (void)updateLastFileSize {
NSError *error = nil;
NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:self.filePath error:&error];
if (error) {
NSLog(@"Error getting file attributes: %@", error);
[self logMessage:[NSString stringWithFormat:@"Error getting file attributes: %@", error]];
return;
}
self.lastFileSize = [attributes fileSize];
}

- (void)handleFileChange {
[self.fileHandle seekToFileOffset:self.lastFileSize];
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:self.filePath];
[fileHandle seekToFileOffset:self.lastFileSize];

NSData *newData;
if (@available(macOS 10.15, *)) {
NSError *error = nil;
newData = [self.fileHandle readDataToEndOfFileAndReturnError:&error];
if (error) {
NSLog(@"Error reading file: %@", error);
return;
}
} else {
newData = [self.fileHandle readDataToEndOfFile];
NSData *newData = [fileHandle readDataToEndOfFile];
[fileHandle closeFile];

if (newData.length > 0) {
[self processNewData:newData];
}

[self updateLastFileSize];
}

- (void)processNewData:(NSData *)newData {
NSString *newContent = [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding];
if (!newContent) {
[self logMessage:@"Error: Unable to decode data as UTF-8"];
return;
}

if (newContent) {
NSArray *lines = [newContent componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableArray *cleanLines = [NSMutableArray array];

for (NSString *line in lines) {
if (line.length > 0) {
NSData *lineData = [line dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
NSString *cleanLine = [[NSString alloc] initWithData:lineData encoding:NSUTF8StringEncoding];
[cleanLines addObject:cleanLine];
}
}

if (cleanLines.count > 0) {
[self outputLines:cleanLines];
NSArray *lines = [newContent componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
NSMutableArray *cleanLines = [NSMutableArray array];

for (NSString *line in lines) {
if (line.length > 0) {
[cleanLines addObject:line];
}
}

[self updateLastFileSize];
if (cleanLines.count > 0) {
[self outputLines:cleanLines];
}
}

- (void)outputLines:(NSArray *)lines {
NSError *jsonError;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:lines options:0 error:&jsonError];
if (jsonData) {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

// Output to stdout
printf("%s\n", [jsonString UTF8String]);
fflush(stdout); // Ensure the output is flushed immediately
fflush(stdout);

// Output to file
if (self.outputFilePath) {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.outputFilePath];
if (!fileHandle) {
[[NSFileManager defaultManager] createFileAtPath:self.outputFilePath contents:nil attributes:nil];
fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.outputFilePath];
}
[fileHandle seekToEndOfFile];
[fileHandle writeData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle writeData:[@"\n" dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
}
} else {
NSLog(@"Error creating JSON: %@", jsonError);
[self logMessage:[NSString stringWithFormat:@"Error creating JSON: %@", jsonError]];
}
}

- (void)logMessage:(NSString *)message {
fprintf(stderr, "message:%s\n", [message UTF8String]);
fflush(stderr);
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
// Set stdout to line buffering
setvbuf(stdout, NULL, _IOLBF, 0);

if (argc != 2) {
NSLog(@"Usage: %s <file_path>", argv[0]);
fprintf(stderr, "message:Usage: %s <file_path>\n", argv[0]);
return 1;
}

NSString *filePath = [NSString stringWithUTF8String:argv[1]];
FileWatcher *watcher = [[FileWatcher alloc] initWithFilePath:filePath];
[watcher startWatching];
[[NSRunLoop currentRunLoop] run];

dispatch_main();
}
return 0;
}

0 comments on commit ed9fee5

Please sign in to comment.