/* -*-ObjC-*-
 ******************************************************************************
 *
 * File:         open.m
 * RCS:          /usr/local/lib/cvs/open/open.m,v 1.14 1995/05/08 18:37:59 chris Exp
 * Description:  replacement for /usr/bin/open
 * Author:       Christian Limpach <chris@nice.ch>
 * Created:      Wed Mar 20 23:48:35 1994
 * Modified:     Mon May  8 20:28:24 1995 (Christian Limpach) chris@nice.ch
 * Language:     ObjC
 * Package:      open
 * Status:       Released
 *
 * (C) Copyright 1994, Christian Limpach, all rights reserved.
 *
 ******************************************************************************
 */

/*
 * open.m,v
 * Revision 1.14  1995/05/08  18:37:59  chris
 * made -as option sticky, it will now remain in effect until an
 * application is explicitely selected via the -a option.
 * this is version 1.3 now
 *
 * Revision 1.13  1995/05/08  17:50:08  chris
 * made -as symlink generation reuse existing symlinks if possible
 * and use the basename of the file instead of openfiletmp as the
 * symlink template [suggested by Peter Langston <psl@acm.org>]
 *
 * Revision 1.12  1995/05/08  15:44:11  chris
 * fixed double tempfile when -as and stdin
 * [reported by Peter Langston <psl@acm.org>]
 *
 * Revision 1.11  1995/05/02  22:28:07  chris
 * changed version number to 1.2
 *
 * Revision 1.10  1995/05/02  22:18:15  chris
 * added -as option to define extensions of files without or with
 * incorrect extensions [suggested by Peter Langston <psl@acm.org>]
 *
 * Revision 1.9  1995/04/05  01:10:22  chris
 * fixed problem with uninitialized stat parameter
 * [reported by Daniel G. Kluge <daniel@vis.inf.ethz.ch>]
 *
 * Revision 1.8  1995/03/30  17:37:02  chris
 * fixed bug with openfiletmpXXXXXX.ext where NXGetTempFilename replaced
 * XX.ext insted of XXXXXX. added documentation for -pause and -connectp.
 * increased version number to 1.1.
 *
 * Revision 1.7  1994/09/01  14:58:24  chris
 * added -connectp (check if we can connect to a Windowserver) and
 * -pause n to make open pause n seconds before exiting
 *
 * Revision 1.6  1994/06/20  22:19:32  chris
 * added -tmpdir option to choose directory used for temporary files
 *
 * Revision 1.5  1994/05/30  22:02:55  chris
 * added -help and -version
 *
 * Revision 1.4  1994/05/26  12:47:39  chris
 * added `Foo.client somefile' = `open -a Foo -wait somefile'
 * [suggested by Carl Edman <cedman@princeton.edu>]
 *
 * Revision 1.3  1994/05/23  18:00:17  chris
 * fixed bug which broke 'open -p *.txt' (only the first file was
 * printed) [reported by Paul van der Zwan <paulzn@olivetti.nl>]
 * fixed bug which broke -NXHost (no more Application object)
 * [reported by Bill Bumgarner <bbum@friday.com>]
 *
 * Revision 1.2  1994/05/09  21:37:42  chris
 * fixed NXHost which was broken because of [Application new]
 *
 * Revision 1.1  1994/05/05  22:32:05  chris
 * replaces /usr/bin/open and adds some nice features
 *
 */

#ifdef RCSID
static char rcsid[] = "open.m,v 1.14 1995/05/08 18:37:59 chris Exp";
#endif

#define VERSION_STRING "open v1.3\n"

#import "open.h"

@implementation AppInfo

- initFor:(const char *)openApp onHost:(const char *)hostName
{
  [super init];
  if((Port=NXPortFromName(openApp, hostName)) != PORT_NULL)
  {
    Name=str_copy(openApp);
    Host=str_copy(hostName);
  }
  return self;
}

- (port_t)port
{
  return Port;
}

- (BOOL)isEntryFor:(const char *)openApp:(const char *)hostName
{
  if(!strcmp(Name, openApp) &&
     (Host==hostName || (Host!=NULL && hostName!=NULL &&
			 !strcmp(Host, hostName))))
    return YES;
  else
    return NO;
}

@end

@implementation AppList

- (port_t)getPortFor:(const char *)anApp onHost:(const char *)aHost
{
  int index;
  
  for(index=0; index<[self count]; index++)
    if([[self objectAt:index] isEntryFor:anApp:aHost])
      break;
  if(index!=[self count])
    return [[self objectAt:index] port];
  else {
    id info;
    
    info=[[AppInfo alloc] initFor:anApp onHost:aHost];
    if ([info port]!=PORT_NULL)
      [self addObject:info];
    return [info port];
  }
}

@end

void version()
{
  fprintf(stderr, VERSION_STRING);
}

int check_help(int argc, char *argv[], const char *appName, int flag)
{
  if(argc>1 && !strcmp(argv[1], "-help"))
  {
    if(flag==HELP_UNHIDE)
      fprintf(stderr, "%s [app] ", appName);
    else if(flag==HELP_APPOPEN)
      fprintf(stderr, "%s app ", appName);
    else if(flag==HELP_OPENAS)
      fprintf(stderr, "%s extension ", appName);
    else 
      fprintf(stderr, "%s ", appName);
    fprintf(stderr, "[-a app] [-o file] [-p] "
	    "[-NXHost hostname] [-unhide] [-nostat] [-wait] "
	    "[-temp] [-tmpdir directory] [-connectp] [-pause n] "
	    "[-as extension] [+linenum] [filename] ...\n");
    return 1;
  } else if(argc>1 && !strcmp(argv[1], "-version")) {
    version();
    return 1;
  } else
    return 0;
}

void main(int argc, char *argv[])
{
  port_t appPort;
  int ok, arg=1;
  char *host=NULL;
  const char *openApp=WORKSPACE, *appName, *tmpDir="/tmp", *asExt="";
  id appSpeaker = [[Speaker alloc] init];
  id appList = [[AppList alloc] init];
  int openMode=OPEN, fileMode=DISK, openedAFile=0;
  int oline=0, waitForFileChange=0, noStat=0, openAs=0;
  
  if(appName=strrchr(argv[0], '/'))
    appName++;
  else
    appName=argv[0];
  
  if(strcmp(appName, "open"))
    if(!strcmp(appName, "appopen") || !strcmp(appName, "run"))
    {
      if(check_help(argc, argv, appName, HELP_APPOPEN)) {
	arg++;
	openedAFile++;
      } else if(argc<(arg+1)) {
	printf("Usage: %s Application file...\n", appName);
	exit(1);
      }
      openApp=argv[arg++];
    } else if(!strcmp(appName, "unhide")) {
      if(check_help(argc, argv, appName, HELP_UNHIDE)) {
	arg++;
	openedAFile++;
      }
      if(argc>arg)
	openApp=argv[arg++];
      openMode=UNHIDE;
    } else if(strlen(appName)>CLIENTEXTLEN &&
	      !strcmp(&appName[strlen(appName)-CLIENTEXTLEN],
		      CLIENTEXT)) {
      register char *name;
	
      waitForFileChange++;
      name=str_copy(appName);
      name[strlen(name)-CLIENTEXTLEN]='\000';
      openApp=name;
      if(check_help(argc, argv, appName, HELP_OPEN)) {
        arg++;
        openedAFile++;
      }
    } else if(!strcmp(appName, "openAs")) {
      if(check_help(argc, argv, appName, HELP_OPENAS)) {
	arg++;
	openedAFile++;
      } else if(argc<(arg+1)) {
	printf("Usage: %s extension file...\n", appName);
	exit(1);
      }
      asExt=argv[arg++];
      openAs++;
    } else {
      openApp=appName;
      if(check_help(argc, argv, appName, HELP_OPEN)) {
	arg++;
	openedAFile++;
      }
    }
  else
    if(check_help(argc, argv, appName, HELP_OPEN)) {
      arg++;
      openedAFile++;
    }
  
  while(1)
  {
    int argIsFile=0;
    
    if(arg>=argc)
      /* auto detect stdin */
      if(openedAFile)
	break;
      else {
	if((openApp!=WORKSPACE && openMode==OPEN) || openMode==UNHIDE)
	{
	  if(openMode==OPEN)
	    openMode=APP;
	  fileMode=NONE;
	} else
	  fileMode=STDIN;
	argIsFile++;
      }
    else if(argv[arg][0]=='-') {
      if(!strcmp(&argv[arg][1], "NXHost"))
      {
	if(arg!=(argc-1))
	  host=argv[++arg];
	else argIsFile++;
      } else if(!strcmp(&argv[arg][1], "wait")) {
	waitForFileChange++;
      } else if(!strcmp(&argv[arg][1], "nostat")) {
	noStat++;
      } else if(!strcmp(&argv[arg][1], "unhide") ||
		!strcmp(&argv[arg][1], "activate") ||
		!strcmp(&argv[arg][1], "makeKey")) {
	openMode=UNHIDE;
	fileMode=NONE;
	argIsFile++;
      } else if(!strcmp(&argv[arg][1], "temp")) {
	fileMode=TEMP;
      } else if(!strcmp(&argv[arg][1], "version")) {
        version();
      } else if(!strcmp(&argv[arg][1], "tmpdir")) {
	if(arg!=(argc-1))
	  tmpDir=argv[++arg];
	else argIsFile++;
      } else if(!strcmp(&argv[arg][1], "connectp")) {
	if(NXPortFromName(WORKSPACE, host) != PORT_NULL)
	  exit(0);
	else
	  exit(1);
      } else if(!strcmp(&argv[arg][1], "pause")) {
	if(arg!=(argc-1))
	  sleep(atoi(argv[++arg]));
	else argIsFile++;
      } else if(!strcmp(&argv[arg][1], "as")) {
        if(arg!=(argc-1)) {
          openAs++;
          asExt=argv[++arg];
        } else argIsFile++;
      } else if(argv[arg][1]=='\000') {
	if(arg==(argc-1) && !openedAFile)
	  fileMode=STDIN;
	argIsFile++;
      } else if(argv[arg][2]=='\000') {
	switch(argv[arg][1]) {
	case 'a': /* next arg is new application to open with */
	  if(arg!=(argc-1)) {
	    openApp=argv[++arg];
            openAs=0;
	  } else argIsFile++;
	  break;
	case 'o': /* open next arg without checking switches */
	  openMode=OPEN;
	  if(arg!=(argc-1))
	    arg++;
	  argIsFile++;
	  break;
	case 'p': /* go printmode */
	  openMode=PRINT;
	  break;
	default: /* not a switch -> open it */
	  argIsFile++;
	  break;
	}
      } else argIsFile++; /* not a longopt -> open it */
    } else if(argv[arg][0]=='+') {
      int i=0;
      
      oline=0;
      while(argv[arg][++i]!='\0')
	if(isdigit(argv[arg][i]))
	  oline=10*oline+(argv[arg][i]-48);
	else {
	  argIsFile++;
	  break;
	}
      if(!argIsFile)
      {
	if(arg!=(argc-1))
	{
	  openMode=OPENLINE;
	  arg++;
	}
	argIsFile++;
      }
    } else argIsFile++; /* not an opt at all -> open it */

    if(argIsFile)
    {
      char fname[MAXPATHLEN+1+(arg>=argc?0:strlen(argv[arg]))+
                 (openAs?strlen(asExt):0)+1];
      struct stat buf;
      
      switch(fileMode) {
      case STDIN: {
	char buffer[STDINBUFSIZE+1];
	int len, xxpos;
	FILE *file;
	
	if((len=fread(buffer, 1, STDINBUFSIZE, stdin))>=0)
	{
	  if(tmpDir[0]=='/')
	    strcpy(fname, tmpDir);
	  else {
	    getwd(fname);
	    strcat(fname, "/");
	    strcat(fname, tmpDir);
	  }
	  if(fname[strlen(fname)-1]!='/')
	    strcat(fname, "/");

          strcat(fname, "openfiletmpXXXXXX");
          xxpos=strlen(fname)-6;
	  buffer[len]='\0';
          if (!openAs) {
            if(len>=5 && !strncmp(buffer, "{\\rtf", 5))
              strcat(fname, ".rtf");
            else if(len>=11 && !strncmp(buffer, "%!PS-Adobe-", 11))
              if(strstr(buffer, "EPSF-")<strchr(buffer, '\012'))
                strcat(fname, ".eps");
              else
                strcat(fname, ".ps");
          } else {
            strcat(fname, ".");
            strcat(fname, asExt);
          }
	  
	  NXGetTempFilename(fname, xxpos);
	  if(!(file=fopen(fname, "wb")))
	  {
	    printf("%s: couldn't create temp file\n", appName);
	    exit(1);
	  }
	  while(len==STDINBUFSIZE)
	  {
	    fwrite(buffer, STDINBUFSIZE, 1, file);
	    len=fread(buffer, 1, STDINBUFSIZE, stdin);
	  }
	  fwrite(buffer, 1, len, file);
	  fclose(file);
	}
	break;
      }
      case NONE:
        break;
      case DISK:
      case TEMP:
      default:
        if(argv[arg][0]=='/')
          strcpy(fname, argv[arg]);
        else {
          getwd(fname);
          strcat(fname, "/");
          strcat(fname, argv[arg]);
        }
        if(fname[strlen(fname)-1]=='/')
          strcat(fname, ".");
        break;
      }

      if(openAs && fileMode!=STDIN) {
        char *tmp, fname2[MAXPATHLEN+1+(arg>=argc?0:strlen(argv[arg]))+
                          strlen(asExt)+1],
          fname3[MAXPATHLEN];
        int count=1, cc;
        
        if(tmpDir[0]=='/')
          strcpy(fname2, tmpDir);
        else {
          getwd(fname2);
          strcat(fname2, "/");
          strcat(fname2, tmpDir);
        }
        if(fname2[strlen(fname2)-1]!='/')
          strcat(fname2, "/");

        tmp=fname2+strlen(fname2);
        if(strrchr(fname, '/')) {
          strcat(fname2, strrchr(fname, '/')+1);
          if(tmp=strrchr(tmp, '.'))
            *tmp='\0';
        } else
          strcat(fname2, "openfiletmp");
          
        tmp=fname2+strlen(fname2);
        sprintf(tmp, "%06d.%s", count++, asExt);
        while(!stat(fname2, &buf)) {
          if(buf.st_mode & S_IFLNK) {
            cc=readlink(fname2, fname3, MAXPATHLEN);
            if(!strncmp(fname, fname3, cc)) {
              tmp=NULL;
              break;
            }
          }
          sprintf(tmp, "%06d.%s", count++, asExt);
        }
        if(tmp) {
          symlink(fname, fname2);
        }

        strcpy(fname, fname2);
      }
      if(!noStat && openMode!=APP && openMode!=UNHIDE && stat(fname, &buf))
	printf("%s: can't stat file: %s\n", appName, fname);
      else {
	char *connName;
        NXAtom type;

	if((openMode==PRINT || openMode==OPENLINE) &&
	   !strcmp(openApp, WORKSPACE))
	  [[Application workspace] getInfoForFile:fname
                                   application:&connName type:&type];
	else
	  connName=str_copy(openApp);
	if((appPort=[appList getPortFor:connName onHost:host]) !=
	   PORT_NULL)
	{
	  [appSpeaker setSendPort:appPort];
	  switch(openMode) {
	  case UNHIDE:
	    [appSpeaker performRemoteMethod:"unhide"];
	    break;
	  case APP:
	    break;
	    
	  case OPENLINE:
	    [appSpeaker selectorRPC:"openFile:onHost:atTrueLine:"
                        paramTypes:"cci", fname,
                        host?host:"localhost", oline];
	    break;
	  case PRINT:
	    if([appSpeaker msgPrint:fname ok:&ok] || !ok)
	      printf("%s: unable to print file: %s\n", appName, fname);
	    break;
	  case OPEN:
	  default:
	    if(fileMode==STDIN || fileMode==TEMP || openAs)
	    {
	      if([appSpeaker openTempFile:fname ok:&ok] || !ok)
		printf("%s: unable to open temp file: %s\n", appName,
		       fname);
	    } else {
	      if([appSpeaker openFile:fname ok:&ok] || !ok)
		printf("%s: unable to open file: %s\n", appName,
		       fname);
	    }
	    break;
	  }
	  if(!noStat && waitForFileChange)
	  {
	    time_t last=buf.st_mtime;
	    
	    while(buf.st_mtime==last && !sleep(1))
	      stat(fname, &buf);
	  }
	} else {
	  printf("%s: can't open connection to %s on %s.\n",
		 appName , openApp, host?host:"local host");
	}
	str_free(connName);
      }
      waitForFileChange=0;
      noStat=0;
      fileMode=DISK;
      if(openMode!=PRINT)
	openMode=OPEN;
      openedAFile++;
    }
    arg++;
  }
  
  [appSpeaker free];
  exit(0);
}
