Sunday, November 19, 2017

#1 2016-12-10 11:09:30 am

kamel3d
Member
From:: London
Registered: 2016-10-03
Posts: 22

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

Applescript:


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


Filed under: multithreading

Offline

 

#2 2016-12-10 12:11:35 pm

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11482
Website

Re: Multithreading in ApplwscriptObjc

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

#import 

@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

* Caution. The syntax highlighter ignores text in < > characters. Replace the weak line with

Applescript:

@property (weak) id<FSEDirectoryWatcherDelegate> delegate;

• Replace the contents of the .m file with

#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: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];
        
    }];
}

• On the AppleScriptObjC side create a property

Applescript:

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.

Applescript:

on directoryDidChange:filePath
   log filePath
end directoryDidChange:

• Finally add this code to start the watcher

Applescript:

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

Last edited by StefanK (2016-12-10 12:22:27 pm)


regards

Stefan

Offline

 

#3 2016-12-10 02:25:19 pm

kamel3d
Member
From:: London
Registered: 2016-10-03
Posts: 22

Re: Multithreading in ApplwscriptObjc

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

Offline

 

#4 2016-12-10 02:32:15 pm

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11482
Website

Re: Multithreading in ApplwscriptObjc

AppleScript is single threaded. It cannot handle asynchronous tasks.

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


regards

Stefan

Offline

 

#5 2016-12-10 10:22:00 pm

kamel3d
Member
From:: London
Registered: 2016-10-03
Posts: 22

Re: Multithreading in ApplwscriptObjc

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 <FILENAME>"

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

Offline

 

#6 2016-12-11 04:06:18 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11482
Website

Re: Multithreading in ApplwscriptObjc

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

Applescript:

#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


regards

Stefan

Offline

 

#7 2016-12-13 06:58:45 am

kamel3d
Member
From:: London
Registered: 2016-10-03
Posts: 22

Re: Multithreading in ApplwscriptObjc

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?

Offline

 

#8 2016-12-13 07:26:59 am

StefanK
Member
From:: St. Gallen, Switzerland
Registered: 2006-10-21
Posts: 11482
Website

Re: Multithreading in ApplwscriptObjc

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

.h file

Add

Applescript:

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

Change the @property .. *directory line to

Applescript:

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

.m file

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

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

- (id)initWithPaths:(NSArray *)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;
}

Change the first line in (void)start to

Applescript:

NSArray *pathsToWatch = self.directories;

In AppleScript you can initialize the watcher

Applescript:

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


regards

Stefan

Offline

 

Board footer

Powered by FluxBB

RSS (new topics) RSS (active topics)