Multithreading in ApplwscriptObjc

Hi guys
I have this app where it should watch a folder and move files/subfolders from it periodically,
I want it to run in the background but for now, it blocks the app until it finishes the task, how can I make it run in BG?
and is it possible to get a notification when a new item is in the folder I am watching, instead of checking constantly in a loop
here ismy code


on startWatching_(sender)
    repeat
        tell application "Finder"
            set sourceFolder to "/Volumes/SSD/01/"
            set targetFolder to "/Volumes/SSD/02/"
            if items of (POSIX file sourceFolder as alias) is not {} then
                do shell script " mv " & sourceFolder & "* " & targetFolder
            end if         
        end tell
    end repeat
end startWatching_

thanks

HI,

tell, not ask. Polling is very very bad.

With a little help from Objective-C (FSEvents) let the operating system tell you when a directory changes

¢ Create an Objective-C class named FSEDirectoryWatcher

¢ Replace the contents of the .h file with

[format]#import <Foundation/Foundation.h>

@class FSEDirectoryWatcher;

@protocol FSEDirectoryWatcherDelegate

  • (void)directoryDidChange:(NSString *)path;
    @end

@interface FSEDirectoryWatcher : NSObject

  • (id)directoryWatcherWithPath:(NSString *)path delegate:(id)delegate;
  • (id)initWithPath:(NSString *)path delegate:(id)delegate;

  • (void)start;

  • (void)stop;

@property (copy) NSString *directory;
@property (weak) id delegate;

@end[/format]

  • Caution. The syntax highlighter ignores text in < > characters. Replace the weak line with
@property (weak) id<FSEDirectoryWatcherDelegate> delegate;

¢ Replace the contents of the .m file with

[format]#import “FSEDirectoryWatcher.h”

static void fileSystemEventCallback(ConstFSEventStreamRef streamRef, void *userData, size_t numEvents, void *eventPaths,
const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]);

@interface FSEDirectoryWatcher ()
{
dispatch_queue_t queue;
FSEventStreamRef stream;
}
@end

@implementation FSEDirectoryWatcher

  • (id)directoryWatcherWithPath:(NSString *)path delegate:(id)delegate

{
return [[FSEDirectoryWatcher alloc] initWithPath:path delegate];
}

  • (id)initWithPath:(NSString *)path delegate:(id)delegate
    {
    self = [super init];
    if (self) {
    _directory = path;
    _delegate = delegate;
    queue = dispatch_queue_create(“com.myself.FSDirectoryWatcher”, DISPATCH_QUEUE_SERIAL);
    }
    return self;
    }

  • (void) dealloc
    {
    [self stop];
    }

  • (void)start
    {
    NSArray *pathsToWatch = @[self.directory];
    FSEventStreamContext context = {0, (__bridge void )self, NULL, NULL, NULL};
    CFAbsoluteTime latency = 3.0; /
    Latency in seconds */

    stream = FSEventStreamCreate(NULL, &fileSystemEventCallback, &context, (__bridge CFArrayRef)pathsToWatch,
    kFSEventStreamEventIdSinceNow, latency, kFSEventStreamCreateFlagUseCFTypes);

    FSEventStreamSetDispatchQueue(stream, queue);
    FSEventStreamStart(stream);
    }

  • (void)stop
    {
    if (stream) {
    FSEventStreamStop(stream);
    FSEventStreamInvalidate(stream);
    FSEventStreamRelease(stream);
    stream = nil;
    }
    }

@end

static void fileSystemEventCallback(ConstFSEventStreamRef streamRef, void *userData, size_t numEvents, void *eventPaths,
const FSEventStreamEventFlags eventFlags[], const FSEventStreamEventId eventIds[]) {
FSEDirectoryWatcher *directoryWatcher = (__bridge FSEDirectoryWatcher *)userData;
NSArray *paths = (__bridge NSArray *)eventPaths;

[paths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if (directoryWatcher.delegate) [directoryWatcher.delegate directoryDidChange:obj];
	
}];

}
[/format]

¢ On the AppleScriptObjC side create a property

property watcher : missing value

¢ And implement this delegate handler which is called when the directory contents change. Instead of the log command add your logic to move the files. The handler will also be called when items are removed so the check for empty directory is very important.

on directoryDidChange:filePath
	log filePath
end directoryDidChange:

¢ Finally add this code to start the watcher

set watcher to current application's FSEDirectoryWatcher's alloc()'s initWithPath:"/Volumes/SSD/01/" delegate:me
watcher's |start|()

Thank you stefan for this effort, there no Applescriptobjc only solution? I try to avoide objective c as much as possible

AppleScript is single threaded. It cannot handle asynchronous tasks.

An exception is the on idle handler but that’s just another way for polling.

Hi Stefan,
I have no idea what have you done in objective c, I have done what you told me and now I have 2 errors in my code and I don’t know how to fix them, I never wrote anything in objectve c
the first error in .h file says “FSEDirectoryWatcher.h:9:8: Expected “FILENAME” or ”

the second one in the .m file says: “FSEDirectoryWatcher.m:21:1: Cannot synthesize weak property in file using manual reference counting”

maybe there a solution using the idel…

thanks

Darn, as I wrote in the caution line, text in < > is ignored, the first line in the .h file is

#import <Foundation/Foundation.h>

To fix the second error select the project file in the navigator, then click on the target icon. In Build Settings type count in the search field and set Objective-C Automatic Reference Counting to Yes

Thank you stefan it workes perfectly, I have 2 questions now:
1- how to make the script stop watching that folder?
2- is it possible to make an instance and watch another folder in same time?

  1. There is also a stop method
  2. Yes, but that needs a few additional lines of code

.h file

Add

- (id)initWithPaths:(NSArray<NSString *> *)paths delegate:(id)delegate;

Change the @property … *directory line to

@property (copy) NSArray<NSString *> *directories;

.m file

Change the initWithPath method to (including adding the initWithPaths method)

[format]- (id)initWithPath:(NSString *)path delegate:(id)delegate
{
return [self initWithPaths:@[path] delegate];
}

  • (id)initWithPaths:(NSArray<NSString *> *)paths delegate:(id)delegate
    {
    self = [super init];
    if (self) {
    _directories = paths;
    _delegate = delegate;
    queue = dispatch_queue_create(“com.myself.FSDirectoryWatcher”, DISPATCH_QUEUE_SERIAL);
    }
    return self;
    }[/format]

Change the first line in bstart[/b] to

NSArray *pathsToWatch = self.directories;

In AppleScript you can initialize the watcher

set watcher to current application's FSEDirectoryWatcher's alloc()'s initWithPaths:{"/Volumes/SSD/01/", "/another/path/"} delegate:me