I have the following code (below) in my app, which simply tries to use NSTask to touch one file with the directory time/date stamp. It works just fine in an app which only accesses one directory all the time, however, it doesn't with another that frequently changes directories to access some of it's data. When I check the currentfile and currentpath they both show the correct paths. I've expired every possibility I can think of; any help would be great appreciated — thank you.
- (void)someMethod:(NSString *)currentfile {
NSFileManager *filemanager = [[NSFileManager alloc] init];
if ([filemanager changeCurrentDirectoryPath: #"/"] == NO)
NSLog (#"Cannot change directory.\n");
NSString *currentpath = [filemanager currentDirectoryPath];
NSLog (#"Current directory is %#", currentpath);
[filemanager release];
NSArray*arguments = [NSArray arrayWithObjects:#"-r",currentpath,currentfile,nil];
[self touchFiles:arguments];
return;
}
- (void)touchFiles:(NSArray *)arguments {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/touch"];
[task setArguments:arguments];
[task launch];
[task release];
return;
}
You can change a file's modification time without using NSTask. Use -[NSURL setResourceValue:forKey:error:] with the key NSURLContentModificationDateKey.
As to why your use of NSTask and touch is failing, perhaps you don't have permissions to modify the file's modification time. Check the console log to see if any error was reported from touch or redirect the task's standard error output to someplace else and check that.
Related
I've been struggling to find a solution to do what should be a very simple task. I need to move a certain type of file (all zip files in this case) into another directory. I've tried NSTask and NSFileManager but have come up empty. I can move one at a time, but I would like to move them in one shot, at the same time.
- (void)copyFilesTo :(NSString*)thisPath {
NSFileManager *manager = [NSFileManager defaultManager];
NSDirectoryEnumerator *direnum = [manager enumeratorAtPath:thisPath];
NSString *filename = nil;
while ((filename = [direnum nextObject] )) {
if ([filename hasSuffix:#".zip"]) {
[fileManager copyItemAtPath:thisPath toPath:newPath];
}
}
}
FAILED - files copied = zeroooo
- (void)copyFilesMaybe :(NSString*)thisPath {
newPath = [newPath stringByAppendingPathComponent:fileName];
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/find"];
[task waitUntilExit];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: thisPath, #"-name", #"*.zip", #"-exec", #"cp", #"-f", #"{}", newPath, #"\\", #";", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
}
Same sad result, no files copied. What the heck am I doing wrong?
In the first case, you aren't using filename in your copy call. You need to construct a full path to the file by combining filename with thisPath and attempting to copy that. Also, the method is -copyItemAtPath:toPath:error:. You left off the last parameter. Try:
NSError* error;
if (![fileManager copyItemAtPath:[thisPath stringByAppendingPathComponent:filename] toPath:newPath error:&error])
// handle error (at least log error)
In the second case, I think your arguments array is wrong. I'm not sure why it includes #"\\". I suspect because at a shell you have to escape the semicolon with a backslash (\;). However, the need to escape the semicolon is because the shell would otherwise interpret it and not pass it to find. Since you're not using a shell, you don't need to do that. (Also, if you did need to escape it, it shouldn't be a separate element of the arguments array. It would be in the same element as the semicolon, like #"\\;".)
Also, are you sure the task has completed? You show the launch but you don't show observing or waiting for its termination. Given that you've set a pipe for its output, you have to read from that pipe to be sure that the subprocess isn't getting stuck writing to it.
I'm not sure why you're calling -waitUntilExit before launching the task. That may be harmless, though.
I am new to objetive-c as I came from Java (Android) and then a little of AppleScript.
So my app is making a git commit. BUT as you know the terminal has output that the user may want to see. So should I continue to use my NSTask for behind the scenes stuff, or should I just use AppleScript and let the user carry on from the terminal. Mainly my push.m looks like this :
#import "Push.h"
#implementation Push
#synthesize test;
#synthesize dirPath;
-(IBAction)chooseFolder:(id)sender{
dirPath = [self get];
NSArray *array = [dirPath componentsSeparatedByString:#"/"];
NSString *pub = [array lastObject];
[projectName setStringValue:pub];
BOOL fileyn = [self check:dirPath];
if(fileyn) {
} else {
}
}
-(IBAction)pushAction:(id)sender {
[self push];
[self push];
}
-(void)push{
if(dirPath == nil || dirPath == #"") {
[self chooseFolder:nil];
}
NSString *commitText = [commitMessage stringValue];
[commitMessage setStringValue:#""];
commitText = [NSString stringWithFormat:#"%#",commitText];
if (i == 1) {
} else {
[self runScript:dirPath:#"add" :#"*" :nil];
[self runScript:dirPath:#"commit" :#"-m" :commitText];
[self runScript:dirPath:#"push" :#"origin" :#"HEAD"];
}
}
-(void) runScript:(NSString *) path:(NSString* )cmd1:(NSString *) cmd2:(NSString *) cmd3{
NSTask *aTask = [[NSTask alloc] init];
NSPipe *pipe;
pipe = [NSPipe pipe];
[aTask setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
NSArray* args = [NSArray arrayWithObjects:cmd1,cmd2,cmd3, nil];
[aTask setArguments:args];
[aTask setCurrentDirectoryPath:path];
[aTask setLaunchPath:#"/usr/local/git/bin/git"];
[aTask setArguments:args];
[aTask launch];
[finished setStringValue:#"finished"];
}
-(IBAction)back:(id)sender{
test = [[NSWindowController alloc] initWithWindowNibName:#"POP"];
[test showWindow:self];
[window close];
}
-(BOOL)check:(NSString *) pow{
BOOL isFile = [[NSFileManager defaultManager] fileExistsAtPath:pow isDirectory:NO];
return isFile;
}
-(NSString *)get {
NSOpenPanel *panel = [NSOpenPanel openPanel];
[panel setAllowsMultipleSelection:NO];
[panel setCanChooseDirectories:YES];
[panel setCanChooseFiles:NO];
if ([panel runModal] != NSFileHandlingPanelOKButton) return nil;
return [[panel directoryURL] path];
}
#end
So what should I do? Any thing to improve? Thanks in advance!
This sort of application design is really up to you; there's no right answer here. It depends on what you're trying to accomplish, and what you'd like to allow the user to do. If you simply want to show users the output of whatever commands you're running, you can simply get the output out of the NSTask (using the output pipe you set) and display it in a text view for the user to look through.
However, if you want to run interactive git commands, that gets a little bit more complicated. I'm going on a limb here since your question wasn't very specific about what 'let the user carry on from the terminal' means, so if this isn't what you meant, then let me know. In terms of good user interface and user experience, unless you have no other choice, it's almost never a good idea to force someone to go to a different app to keep using your own. If an app wants to display results to a user, it's best to do it in-app (with a custom view, or a web view, for instance), not to drive the user elsewhere. If you want the commands to be interactive, its far better design to come up with an interface for handling that in your app versus running an AppleScript and directing users to the terminal to see and do things.
Anyway, at a quick glance, your code seems like it should work (except for the aforementioned problems in the comments, which you should fix) — and since you hadn't mentioned any problems with it, I assume it does. This isn't a programming problem; it's a design one, and one you'll have to consider the answers to it yourself (and, that being said, I'm voting to close this question since it doesn't really fit into the StackOverflow guidelines — the answer is a matter of opinion, not of facts).
When i try this code:
if ([[NSFileManager defaultManager] fileExistsAtPath:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] == NO) {
NSMutableDictionary* tempDict = [[NSMutableDictionary alloc]initWithObjectsAndKeys:
[[NSString alloc] initWithString:#"0"],#"completedGames",
[[NSString alloc] initWithString:#"0"],#"floatsCollected",
[[NSString alloc] initWithString:#"0"],#"sec",
[[NSString alloc] initWithString:#"0"],#"subScore",
[[NSString alloc] initWithString:#"0"],#"highScore",
[[NSString alloc] initWithString:#"0"],#"longestSec", nil];
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
}
and this outputs with
2012-04-14 19:15:10.009 Staying Afloat[3227:9c07] written file
so the loop has run, but the file isn't written?
can anyone point my in the right dirrection for saving plists to non-localized places?
EDIT: this is for mac
You need to expand your path using -(NSString *)stringByExpandingTildeInPath when you write and check if the file exist.
Edit: Read the method writeToFile:automatically in the NSDictionary documentation. It says
If path contains a tilde (~) character, you must expand it with stringByExpandingTildeInPath before invoking this method.
So just do something like
[tempDict writeToFile:[[NSString stringWithString:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist"] stringByExpandingTildeInPath] atomically:YES];
first you can't just write :
[tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES];
NSLog(#"written file");
because you don't check if the file was really written with success or not.
you should write :
if([tempDict writeToFile:#"~/Library/Application Support/Staying Afloat/stats/static-stats.plist" atomically:YES]) {
NSLog(#"written file");
} else {
NSLog(#"file not written");
}
WriteToFile method returns a Boolean.
I always use NSUserDefaults which takes care of everything for you with things like user prefs or storing app states between running. Off the top of my head I'd suggest you haven't got the right privileges to write a plist and what happens if you accidentally destroy a vital key and value for the os? Have a look at
Best practices for handling user preferences in an iPhone MVC app?
Are you sure -writeToFile:atomically: returns YES?
I have an application that checks its command line parameters and stores values in persistent stores. One of those is a password that I don't want sticking around for people to see with 'ps' and friends. The approach I'm currently looking at is to, after I've stored the values I need, relaunch the process without the command line parameters. My naive approach is this, where args[0] is the path to the application:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:[args objectAtIndex:0]];
[task launch];
[task release];
[NSApp terminate:nil];
The child is run. However, when my app is terminated the child doesn't seem to orphan but gets stuck. Am I just way off on this one?
More info: So it seems that when I call [NSApp terminate:nil] the NSTask that was launched gets stuck, but if I just exit() then it works fine. However, I'm concerned that things that are open (keychain, plist, etc.) will be in a bad state if I do that.
And note that lots of example code out there is about some watchdog-like process that restarts a separate process when needed. I'm trying to restart the current process that's already running from within that same process.
There are plenty of examples on the web, but this one (also below) looks like it has all the code you need. There are more detailed explanations out there, as well.
// gcc -Wall -arch i386 -arch ppc -mmacosx-version-min=10.4 -Os -framework AppKit -o relaunch relaunch.m
#import <AppKit/AppKit.h>
#interface TerminationListener : NSObject
{
const char *executablePath;
pid_t parentProcessId;
}
- (void) relaunch;
#end
#implementation TerminationListener
- (id) initWithExecutablePath:(const char *)execPath parentProcessId:(pid_t)ppid
{
self = [super init];
if (self != nil) {
executablePath = execPath;
parentProcessId = ppid;
// This adds the input source required by the run loop
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(applicationDidTerminate:) name:NSWorkspaceDidTerminateApplicationNotification object:nil];
if (getppid() == 1) {
// ppid is launchd (1) => parent terminated already
[self relaunch];
}
}
return self;
}
- (void) applicationDidTerminate:(NSNotification *)notification
{
if (parentProcessId == [[[notification userInfo] valueForKey:#"NSApplicationProcessIdentifier"] intValue]) {
// parent just terminated
[self relaunch];
}
}
- (void) relaunch
{
[[NSWorkspace sharedWorkspace] launchApplication:[NSString stringWithUTF8String:executablePath]];
exit(0);
}
#end
int main (int argc, const char * argv[])
{
if (argc != 3) return EXIT_FAILURE;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[[TerminationListener alloc] initWithExecutablePath:argv[1] parentProcessId:atoi(argv[2])] autorelease];
[[NSApplication sharedApplication] run];
[pool release];
return EXIT_SUCCESS;
}
I know its a bit late to answer but this answer may help others. Here is a cool trick that can help you.
By using the terminal command, just open your application as a new instance and terminate the current instance.
This is how it is done:
....
NSString* path = [[NSBundle mainBundle] bundlePath];
NSString* cmd = [NSString stringWithFormat:#"open -n %#", path];
[self runCommand:cmd];
exit(0);
}
/// temrinal function
-(NSString*)runCommand:(NSString*)commandToRun;
{
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments = [NSArray arrayWithObjects:
#"-c" ,
[NSString stringWithFormat:#"%#", commandToRun],
nil];
NSLog(#"run command: %#",commandToRun);
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *output;
output = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
return output;
}
Create an external process that launches yours when it terminates. Then terminate. Launching Cocoa programs with NSTask doesn't work quite right.
For anyone who still wants to use NSTask to relaunch,I found one possible way: Please DO NOT set NSPipe of the NSTask,because the NSTask will terminate the app itself,once the app terminated,the NSTask that started might get stuck there.
At least for me,after I removed the NSPipe settings,my app relaunch successfully.
The following is what I do:
1. Write a command line tool which has 3 parameters: app bundle id,app full path,please note that in this command line you need to terminate the app and wait for a while to make sure it is really terminated before launch the new one,I keep checking app.isTerminated and sleep(1) if it's not terminated.
Launch the Command line tool in app using NSTask,and set the parameters accorddingly,Don't use NSPipe,simply create NSTask and launch
The app relaunches now
I'm running into an issue with relaunching my application on 10.5. In my Info.plist I have LSMinimumSystemVersionByArchitecture set so that the application will run in 64-bit for x86_64 and 32-bit on i386, ppc, and ppc64.
I have a preference in the app that allows the user to switch between a Dock icon & NSStatusItem, and it prompts the user to relaunch the app once they change the setting using using the following code:
id fullPath = [[NSBundle mainBundle] executablePath];
NSArray *arg = [NSArray arrayWithObjects:nil];
[NSTask launchedTaskWithLaunchPath:fullPath arguments:arg];
[NSApp terminate:self];
When this executes on 10.5, however it relaunches the application in 64-bit which is not a desired result for me. From what I gather reading the docs its because the LS* keys are not read when the app is launched via command line.
Is there a way around this? I tried doing something like below, which worked on 10.6, but on 10.5 it was chirping at me that the "launch path not accessible". ([NSApp isOnSnowLeopardOrBetter] is a category that checks the AppKit version number).
id path = [[NSBundle mainBundle] executablePath];
NSString *fullPath = nil;
if (![NSApp isOnSnowLeopardOrBetter])
fullPath = [NSString stringWithFormat:#"/usr/bin/arch -i386 -ppc %#", path];
else
fullPath = path;
NSArray *arg = [NSArray arrayWithObjects:nil];
[NSTask launchedTaskWithLaunchPath:fullPath arguments:arg];
[NSApp terminate:self];
You should instead use the methods of NSWorkspace, which does take into account Info.plist keys. For example, use -(BOOL)launchApplication:(NSString*).
It's because you use spaces inside fullpath, use arguments within an array [NSArray arrayWithObjects:#"/usr/bin/arch",#"-i386",#"-ppc",path,nil].
try this code for relaunch your app:
//terminate your app in some of your method:
[[NSApplication sharedApplication]terminate:nil];
- (void)applicationWillTerminate:(NSNotification *)notification {
if (i need to restart my app) {
NSTask *task = [NSTask new];
[task setLaunchPath:#"/usr/bin/open"];
[task setArguments:[NSArray arrayWithObjects:#"/Applications/MyApp.app", nil]];
[task launch];
}
}