Announcement

Collapse
No announcement yet.

Creating a ProgressBar launcher

Collapse
This topic is closed.
X
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    Creating a ProgressBar launcher

    This is likely to be in several parts. I hope that's ok.

    Each part has some interesting features.

    Features: Running bash commands from C, write and run a bash shell script, a script that 'launches' an app (our own app in this case), a couple of kdialog shell commands.

    Before I go back and re-create my kdevelop3 deb installation a bit
    more correctly, let's see what we can do with a little C programming.

    It uses shell commands, so that part should be interesting to script
    writers too.

    Let's make a daemon to launch, send data to, and watch a ProgressBar
    that we will talk to (for now) through a regular file in the /tmp
    directory.

    Our connection to the daemon will be (essentially) its pid. It's
    connection to the ProgressBar will be the dbus connection returned
    by kdialog --progressbar.

    Overview:

    * bash
    addr=$(ProgressBar title total)
    ...
    to increment:
    let serial=serial+1
    echo "$serial "add" $chunksize" > $addr

    to stop:
    let serial=serial+1
    echo $serial "stop" 0 > $addr

    to end:
    if ! -x $addr # file has been deleted/connection closed

    to pause:
    todo

    * ProgressBar program
    init
    send file address = /tmp/ProgressBar-$pid
    create file
    set up variables
    read pipe "kdialog --title \"%s\" --progressbar %s"
    save dbus address
    run
    begin daemon loop
    check file serial new?
    update widget
    check widget
    closed?
    end loop
    check file
    removed?
    end loop
    cleanup
    remove file

    Part 1 testing the self-launcher.

    First let's create an app that can 'launch' itself independent
    of a terminal. In this test, notice that you can execute
    terminal commands after the app has launched itself and before
    it quits (after 10 seconds).

    THIS TEST TAKES 10 SECONDS TO FINISH

    You might be confused by the launched app poping a notice up
    after you thought the test was done if I hadn't warned you.

    Code:
    // file: main.c
    
    // some commonly used c headers
    #include <stdio.h>  // printf, sprintf, etc.
    #include <stdlib.h>  // exit()
    #include <unistd.h>  // getpid() getppid()..tons of stuff
    #include <malloc.h>  // memory allocation
    #include <string.h>  // strings and block comparisons and moves
    #include <errno.h>  // error numbers
    #include <stddef.h>  // pid/ppid
    #include <stdlib.h>  // canonicalize_file_name()
    
    void dbg(){}     // a breakpoint for kdbg.exec
    
    char scratch[4096];
    
    // recreate commandline from parsed args list
    int unparse_args(char* cmdline, int limit, int argc, char** argv);
    // launch (init) self using restored commandline
    int launch_self(char* cmdline);
    // write a file, possibly executable, from a string/buffer.
    int write_file(const char* path, const char* fname, char* data, int len, int exec);
    
    
    int main(int argc, char** argv) {
     dbg();
     if(getppid() != 1) 
     { // if not parent = init
      char *ptmp, *pstr = scratch;
      int len, err;
      
      // get full path to app, even if ./app used and create
      // shell script text to 'launch' this app again.
      ptmp = canonicalize_file_name(argv[0]);
      pstr += sprintf(pstr, 
          "#/!bin/sh\n"
          "nohup %s ",
          ptmp);
      free(ptmp);
      
      len = pstr - scratch;
      err = unparse_args(pstr, 4096-len, argc, argv);
      if(err)
      {
       perror("Can't unparse args");
       return err;
      }
      
      // update string printer var position and 
      // suppress error and warning messages and
      // return control to terminal immediately with 
      // the shell '&' operator.
      pstr += strlen(pstr);
      pstr += sprintf(pstr, " >/dev/null 2>&1 &");
      
      // use kdialog functions to report current status..
      system("kdialog --msgbox 'ready to launch self'");
      return launch_self(scratch);
     }
     
     // use kdialog functions to report current status in launched app
     system("kdialog --passivepopup 'self is now running\nPlease wait... 10 seconds' '5'");
     sleep(10);
     system("kdialog --msgbox 'self is now stopping after running 10 seconds'");
     return 0;
    }
    
    
    // recreates a commandline from an args list
    int unparse_args(char* cmdline, int limit, int argc, char** argv) 
    {
     cmdline[0] = 0;
     if(argc < 2)  return 0;
     
     char* buf = (char*) malloc(limit+256);
     if(!buf)    return errno = ENOMEM;
     
     char* p = buf;
     char* stopat = buf+limit -1;
     int len;
     
     for(int i = 1; i < argc; i++) {
      p += sprintf(p, "%s ", argv[i]);
      if(p >= stopat) {
       free(buf);
       return errno = E2BIG;
      }
     }
     p[-1] = 0; // terminate
     memcpy(cmdline, buf, p-buf);
     free(buf);
     return 0;
    }
    
    
    // print an error message and return error code
    int errmsg(int errcode, const char* msg) 
    {
     fprintf(stderr, "%s\n", msg);
     return errcode;
    }
    
    
    // write a shell script that will execute a commandline, returning
    // control to the terminal or calling app immediately. Parent of
    // the newly launched app will be 'init'.
    int launch_self(char* cmdline) 
    {
     char cmd[256];
     // if(write_file(getenv("HOME"), "tmp.sh", scratch, strlen(scratch), true))
     if(write_file(getenv("HOME"), "tmp.sh", scratch, strlen(scratch), 1))
      return errmsg(1, "Can't write launcher script");
     
     // ok? now let's execute the script
     sprintf(cmd, "%s/tmp.sh", getenv("HOME"));
     system(cmd);
     return 0;
    }
    
    
    // write a file from data, optionally executable
    int write_file(const char* path, const char* fname, char* data, int len, int exec) 
    {
     char fullname[256];
     char cmd[256];
     sprintf(fullname, "%s/%s", path, fname);
     FILE* fp = fopen(fullname, "w");
     if(!fp) return 1;
     fwrite(data, 1, len, fp);
     fclose(fp);
     if(exec) 
     {
      sprintf(cmd, "chmod +x %s", fullname);
      return system(cmd);
     }
     else
      return 0;
    }
    To compile type:
    gcc -std=gnu99 main.c -o main
    [The gnu99 switch allows index vars to be defined locally inside for(...) loops.]

    To run type:
    main
    [add the old './' prefix if you don't have '.' (dot) in your path.]

    _________________________________________________
    Note: This is REALLY causing the app to re-launch itself with 'init' as its parent. You can verify this in ksysguard. You have 10 seconds to look for it. Currently it isn't doing anything for that ten seconds except waiting. It could just as well be polling the "connection" from the terminal to update variables for the ProgressBar.


    #2
    Re: Creating a ProgressBar launcher

    _________________________________________________
    [If you are looking for the straight bash code for a dbus connection with a kdialog progressbar just skip to the bottom of this post. I don't like it but if it works for you, have at it. :-) ]
    _________________________________________________

    Part 2.

    Features:

    [x] Check if file exists by it's filename. (see 'filename_exists')
    [x] C tricks. The '?' and ':' operators to simplify return values.
    [x] C macros to create temp aliases for function names.
    [x] Using a file (aka lock) to assure only one instance runs.
    [x] More kdialog stuff for feedback from a genuine home made daemon.
    [x] The simplest protocol: Dead or alive?

    For this, you will need to copy and paste the functions below main() in Part 1, so we can keep this post down a reasonable size.

    When you compile it, if you have a missing 'declaration' it's because .. well, that shouldn't happen. The protos are at the top of the new code too.

    If you have a missing 'definition', however, you probably missed a function or two from part 1. Copy/paste and it should work.

    I'll probably finish this up and post a more complete system but this is the fun part. (See notes at the bottom if you are wondering why something like this might be useful.)

    :-)

    Code:
    // file: main
    
    // some commonly used c headers
    #include <stdio.h>  // printf, sprintf, etc.
    #include <stdlib.h>  // exit()
    #include <unistd.h>  // getpid() getppid()..tons of stuff
    #include <malloc.h>  // memory allocation
    #include <string.h>  // strings and block comparisons and moves
    #include <errno.h>  // error numbers
    #include <stddef.h>  // pid/ppid
    
    // an oddball, returns malloc-ed filename, absolute path.
    extern char* canonicalize_file_name(char*);
    
    void dbg(){}     // a breakpoint for kdbg.exec
    
    // Old stuff ------------------------------------------------
    
    // scratchpad buffer
    char scratch[4096];
    
    // recreate commandline from parsed args list
    int unparse_args(char* cmdline, int limit, int argc, char** argv);
    // launch (init) a program using restored commandline
    int launch_self(char* cmdline);
    // write a file, possibly executable, from internal data.
    int write_file(const char* path, const char* fname, char* data, int len, int exec);
    // prints an error message and returns errcode
    int errmsg(int errcode, const char* msg);
    
    
    // Modified stuff ------------------------------------------
    
    // This is based on main() functions in part 1.
    // creates the launcher program we can launch ourself with.
    int create_launcher(int argc, char** argv);
    
    
    // New stuff ------------------------------------------
    
    // returns non-zero if it exists.
    int filename_exists(const char* filename);
    
    // the 'address' or in this case file name for communication.
    char channel_address[256];
    
    // the data sent from an external app.
    char sender_serial[32];
    char sender_command[32];
    char sender_params[256];
    
    // Creates an address (file) using our unique PID 
    // that can be used to communicate to us.
    void init_channel();
    
    // sends 'address' to terminal so we can communicate.
    void send_channel_info();
    
    // checks to see if address has been closed from the
    // other end (i.e., file deleted in this scheme).
    int channel_dead();
    
    // the daemon's communication loop.
    void run_daemon();
    
    /////////////////////////////////////////////////////////////////
    // Part 2. Let's Talk... Kinda brutal form of communication, but 
    // what do you expect at this point? :-)
    
    const char* details_str =
     "\n"
     "TO CONNECT:\n"
     "\n"
     " For this test we'll broadcast the daemon's address in file\n"
     "\n"
     " /tmp/channel-hello\n"
     "\n"
     "In bash, you can get the address by typing\n"
     "\n"
     " addr=$(</tmp/channel-hello)\n"
     "\n"
     " Once you have the address, delete /tmp/channel-hello, signalling \n"
     " that the connection has been made. For now there's no time limit \n"
     " to do this.\n"
     "\n"
     "TO COMMUNICATE:\n"
     "\n"
     " The daemon is still pretty brain dead, but it should be running\n"
     " and viewable in ksysguard (sort by PIDs to find it near the top).\n"
     "\n"
     " To finish the test, break the connection. That is done by simply\n"
     " deleting the 'address' file in this scheme.\n"
     "\n"
     " Type 'rm $addr'. Or 'rm /tmp/channel-test'. The daemon should\n"
     " announce that it is stopping.\n"
     "\n"
     ;
    
    
    void tmp_run_daemon()
    {
     int connected = 0;
     while(! channel_dead())
     {
      if((!connected) && (!filename_exists("/tmp/channel-hello")))
      {
       connected++;
       system("kdialog --passivepopup \"Assuming connected now..\" 2");
      }
      sleep(1);
     }
    }
    
    void tmp_init_channel()
    {
     // TEMPORARY FOR TEST, A SIMPLE FILE NAME WE CAN DELETE. 
     sprintf(channel_address, "/tmp/channel-test");
     write_file("/tmp", "channel-test", (char*)"\n", 1,0);
    }
    
    // Use these tmp versions this time around.
    #define init_channel tmp_init_channel
    #define run_daemon tmp_run_daemon 
    
    
    int main(int argc, char** argv) 
    {
     dbg();
     int err = 0;
     if(getppid() != 1) 
     { 
      if(filename_exists("/tmp/channel-test"))
      {
       printf("\n"
           " A test ProgressBar daemon appears to already be running\n"
           " Delete the file '/tmp/channel-test' and try again\n\n");
       return 1;
      }
      printf("%s", details_str);
      err = create_launcher(argc, argv); // based on main() in part 1
      err = launch_self(scratch);
      // TODO: delete_launcher
      return err;
     }
     
     ///////////////////////////////////////////////////////
     // If we get here we have been 'launched'. Our parent
     // is 'init' (ppid = 1). We have no connection to any 
     // other app including the terminal or the app that 
     // launched us at this point. We will run forever or
     // until stopped by something that can send us a signal
     // of some kind. One signal type is SIGKILL. Another
     // would be a private connection to another app that
     // knows the address of a channel to talk to us through.
     ///////////////////////////////////////////////////////
     
     init_channel();
     send_channel_info(); // /tmp/channel-hello
     system("kdialog --passivepopup \"Test ProgressBar daemon running.\" 2");
     run_daemon();
     system("kdialog --msgbox 'Test ProgressBar daemon shutting down.'");
     return 0;
    }
    
    ////////////////////////////////////////////////////////////
    // The library of functions
    
    // Modified from main() in part 1.
    // creates the launcher program we can launch ourself with
    int create_launcher(int argc, char** argv)
    {
     char *ptmp, *pstr = scratch;
     int len, err;
     
     ptmp = canonicalize_file_name(argv[0]);
     pstr += sprintf(pstr, "#/!bin/sh\nnohup %s ", ptmp);
     free(ptmp);
    
     len = pstr - scratch;
     err = unparse_args(pstr, 4096-len, argc, argv);
     if(err)
     {
      perror("Can't unparse args");
      return err;
     }
    
     pstr += strlen(pstr);
     pstr += sprintf(pstr, " >/dev/null 2>&1 &");
     return 0;
    }
    
    //////////////////////////////////////////////////////
    // New stuff
    
    
    int filename_exists(const char* filename)
    {
     FILE* fp = fopen(filename, "r");
     if(fp)
      fclose(fp);
     // if fp = 0, return 0 else 1
     return fp == 0 ? 0 : 1;
    }
    
    int channel_dead()
    {
     // returns 1 if channel (or file) has been deleted.
     return filename_exists(channel_address) ? 0 : 1;
    }
    
    
    // sends 'address' to terminal so we can communicate.
    void send_channel_info()
    {
     write_file("/tmp", "channel-hello", channel_address, strlen(channel_address), 0);
    }
    
    /////////////////////////////////////////////////////////////////
    // COPY THE FUNCTIONS DEFINED BELOW main() IN PART 1 AND PASTE 
    // THEM IN HERE.
    To compile:
    gcc -std=gnu99 main.c -o main

    To link:
    not necessary yet.

    To test:
    type 'main' (or ./main if you don't have '.' in your path).


    Notes:

    This is actually pretty fun stuff. Hope you get a chance to try some of it.

    If you are wondering why we want to recreate a ProgressBar when kdialog already has one, the answer is this.

    Why not?

    Furthermore we want to have an independent app, hopefully able to update the progress bar based on average speed so when an individual operation may take a very long time, the progress indicator doesn't stall out completely (giving the impression that the operation may have failed).

    We could, of course, do what Ark does too. But then we have no real idea how far along in the process we are.

    So the options for the lowly commandline currently are either accept a choppy progress indicator that tells the actual progress or a smooth running one that doesn't.

    And what if this kind of thing will actually work? :-) Wouldn't that be a kick in the pants. :-) And it might work. It's already doing some of the stuff REAL daemons do.

    I'll post a link to the final version when it's done.

    Here are some of the kinds of dbus commands it will be taking over.

    Code:
    dbusRef=`kdialog --progressbar "Press Cancel at Any time" 10`
    qdbus $dbusRef showCancelButton true
    
    until test "true" = `qdbus $dbusRef wasCancelled`; do
     sleep 1
     inc=$((`qdbus $dbusRef Get "" "value"` + 1))
     qdbus $dbusRef Set "" "value" $inc;
    done
    
    qdbus $dbusRef close
    Nice demo, but pretty useless, no? The clock already runs at a pretty even rate. ;-)

    Comment


      #3
      Re: Creating a ProgressBar launcher

      Bash can send arg lists to C/C++ and they be accurately reproduced for use by an intermediary program, such as a daemon to relay them to another program.

      Here's a argument parser for the progress bar daemon, which may also be useful in other bash to C interfacing.

      Features:
      [x] Interfacing bash with C and C with bash.
      [x] Escape sequence translation from bash-style to C-style.
      [x] Argument parsing with simple way to avoid splitting strings. I.e., we can mix strings with spaces with strings without spaces and also have multi-line strings as single args.
      [x] Basic parsing logic in a practical (well... it will be) example.

      Parsers are fun. A hand-made parser gives us control over the way we handle text and is much more intuitive than a flex/bison generated parser and it's convoluted tokens.

      Here we attempt to take a parameter list passed to C/C++ in the following format:
      Code:
       list="
       item1
       multiple items on a line
       another_item
       \" An explicitly quoted item\nwith an embedded newline \"
       "
      Note that the newline following the first quote following 'list=' will be removed by 'echo' if you 'echo "$list"'. But how will this appear in an external app? (We shall see. Got kdbg?) :-)

      We'll pass the list to C/C++ as a single argument like so:

      Code:
       parse-this "$list"
      where the argument/string terminators are newlines.

      And we want to
      1. remove leading and trailing (+/-) spaces from the list
      items that don't get quoted (for convenience),
      2. remove +/- spaces and place quotes around strings
      containing spaces,
      3. quote all within an explicitly quoted line, including
      quoted leading spaces (but excluding spaces up to the
      first quote (redundant, as we shall see),
      4. convert the text "\n" into real newlines (and translate
      other escape sequences too, while we're at it).


      This is fairly long, but may well be worth sharing. I discovered that bash args are so screwy that I had to come up with a new way to separate them so they can be reformatted and sent to the ProgressBar (revised from first cut).

      When we send this.
      Code:
      list="
       1
       start
        This is your Captain speaking.\nPlease man the lifeboats.
       1000
       "
      like this
      Code:
       <appname> "$list"
      We want it to be accurately reconstructed by the application so it can be sent back to the shell or as in this case, to kdialog --progressbar widget by way of qdbus <params> looking like this:

      Code:
      1 start "This is your Captain speaking.
      Please man the lifeboats." 1000
      Within the quotes above is a multi-line label. Other kde apps do handle these and it may come in handy for the progress bar.

      So, the fields here are.
      1. the serial number of the command sent.
      2. the command to be interpreted by the daemon.
      3. the text/label to set in the progress bar (for 'start' command).
      4. an optional total number of steps to use (again, for 'start' only).

      Numbers one and two are prefixed by the so called "proxy" which will be in a bash routine. They are fields used internally by the daemon. Numbers 3 and 4 are the args list, which can be passed around within the shell as a list as well but I don't want to get into that here because this is already fairly lengthy and the shell example code will make it clear what we're doing with these here 'lists'. :-)

      That said, here's 'parse-this.cpp'. See the code for the compilation tip.

      Code:
      // file: parse-this.cpp - a shell arglist parser
      // To compile: g++ parser.cpp -o parse-this
      // Add -g3 to the switches if you have kdbg and want to see it run.
      
      
      #include <stdio.h>  // printf, sprintf, etc.
      #include <stdlib.h>  // exit()
      #include <unistd.h>  // tons of stuff
      #include <malloc.h>  // memory allocation
      #include <string.h>  // strings and block comparisons and moves
      
      // to switch between testing mode (file) or normal usage.
      #define TESTING 0
      
      void dbg(){}     // a breakpoint for kdbg.exec
      
      #define INPUT_BUFSIZE 4096
      char _input_buffer[INPUT_BUFSIZE];
      char* input_buffer = _input_buffer;
      
      #define MAX_ARGS 256
      // a list of file names could even be larger
      char* _arglist[MAX_ARGS];
      
      // initially use the static array, but always
      // allocate dynamic strings.
      char** arglist = _arglist; 
      
      int arglist_size = 256;
      int arglist_count = 0;
      
      int usage(int errcode);
      
      // pass in a pointer to the base of the argslist for later,
      // in case we want to allocate more args space.
      // returns true on success.
      int parse_args(char*** pargs, 
              int* pargs_max, 
              int* pargs_count, 
              const char* ibuf // never mess with input
             );
      
      
      int main(int argc, char** argv)
      {
       dbg();
       int err;
      #if ! TESTING 
       // make sure we remember how to use this.
       if(argc != 2)
        return usage(1);
       
       strcpy(input_buffer, argv[1]);
      #else
       // copy text from a file so we can test long lists
       FILE* fp = fopen("test.txt", "r");
       fread(input_buffer, 1, INPUT_BUFSIZE, fp);
       fclose(fp);
      #endif
       
       // make this a call with flexible parameter types so 
       // we can reuse the routines, possibly making the array
       // expandable.
       err = ! parse_args(&arglist, 
                &arglist_size, 
                &arglist_count, 
                input_buffer);
       if(err)
        return 1;
       
       for(int i = 0; i < arglist_count; i++)
       {
        printf("%s ", arglist[i]);
       }
       printf("\n");
       
      }
      
      ////////////////////////////////////////////////////////////
      // parser funcs are state-based, returning true when 
      // successful or false when an an attempt to match criteria
      // fails.
      
      // returns (state =) true if successful
      int parse_one_arg(char** pop, const char** pip);
      
      // returns true if syntax works to end of input.
      int parse_args(char*** parglist, 
              int* parglist_size, 
              int* parglist_count,
              const char* ibuf)
      {
       // unpack the inputs needed for a simple static array.
       char** arglist = *parglist;
       int arglist_count = 0; // init here
      
       // non-zero non-TRUE initial state.
       int state = -1;
       
       // create our output buffer and pointer to next string
       char buf[256]; // short strings only in this version.
       const char* ip = ibuf;
       char* op = buf;
       
       
       // until error (or below we handle end of input)
       for(; state != 0; arglist_count++)
       {
        // queue up the next text, skipping empty lines
        // and blanks. Ends loop at end of input.
        while((*ip <= ' ') && (*ip != 0))
         ip++;
        
        if(*ip == 0)
        {   
         if(state < 0)
      	state = 0;
         break;
        }
      
        // start a new string
        op = buf;
        
        state = parse_one_arg(&op, &ip);
        
        // realloc here instead of malloc or strdup so 
        // the array is reusable in other apps.
        if(state)
        {
         // TODO: if the arglist array is dynamic, resize 
         // it here when count >= size.
         
         int len = strlen(buf) + 1;
         char* tmp = arglist[arglist_count];
         tmp = (char*)realloc(tmp, len);
         strcpy(tmp, buf);
         arglist[arglist_count] = tmp;
        }
       }
       
       // update arg count
       *parglist_count = arglist_count;
       return state;
      }
      
      // Since an explicit quote is basically a multi-word line
      // it would be redunant (and misleading).
      // All multi-word lines will be implicitely quoted in C/C++
      
      // an unquoted string contains no spaces
      int single_word(char** pop, const char**pip);
      
      // A quoted string containing spaces
      int multi_word(char** pop, const char**pip);
      
      // advance to next line, pretty useless but looks
      // better in a debugger.
      int opt_next_line(char** pop, const char**pip);
      
      // translate a recognized esc sequence from bash to C
      int xlate_esc(char**pop, const char** pip);
      
      // attempt to identify and process an argument.
      int parse_one_arg(char** pop, const char** pip)
      {
       // unpack vars 
       const char* ip = *pip;
       char* op = *pop;
       int state = 0; 
       *op = 0; 
       
       // empty strings return false but may have other 
       // meaning if empty strings are optional.
       if(*ip == 0) return 0; 
      
       // this format for a series of tests is a kind of
       // A OR B OR C... OR Z kind of thing. It's similar
       // to if(A).. else if(B)... else if(C)... else Z, 
       // but is quite a bit simpler to write, though this is
       // a very short string of OR's and not the best example.
       do
       {
        if((state = single_word(&op, &ip)))
         break;
        state = multi_word(&op, &ip);
       }while(0);
       
       if(! state)
        return 0; // error 
       
       // goto next line and update caller's pointers
       opt_next_line(&op, &ip);
       *pip = ip;
       *pop = op;
       return state; // always true at this point
      }
      
      
      // optional, always true
      int opt_next_line(char** pop, const char**pip)
      {
       const char* ip = *pip;
       
       // don't strchr on a null string.
       if(*ip == 0)
        return 1;
       
       // skip trailing ws to newline or eoi
       while((*ip) && (strchr(" \t", *ip)))
        ip++;
      
       // adv to next line if possible
       if(*ip == '\n')
        ip++;
       
       *pip = ip;
       return 1;
      }
      
      
      // unquoted word or numeric string
      int single_word(char** pop, const char**pip)
      {
       const char* ip = *pip;
       char* op = *pop;
       
       while(*ip > ' ')
       {
        if(!xlate_esc(&op, &ip))
         *op++ = *ip++;
       }
       // terminate string
       *op = 0;
       
       // if blank here, must be blanks to newline or end of input
       while(*ip == ' ')
        ip++;
       
       if(*ip > '\n')
        return false;
       
       *pip = ip;
       *pop = op;
       return true;
      }
      
      
      // quote string containing spaces for C/C++
      int multi_word(char** pop, const char**pip)
      {
       const char* ip = *pip;
       char* op = *pop;
       const char* limit;
       int state = 0; // until otherwise
       
       *op++ = '"';
       
       // find end of string minus trailing
       for(const char* ptmp = ip; *ptmp > '\n'; ptmp++)
       {
        if(*ptmp > ' ')
         limit = ptmp;
       }
       
       // up to and including limit
       for(;ip <= limit;)
       {
        if(*ip == ' ')
         state = 1; // it's multi-word
        
        // translate escaped
        if(*ip == 0x5C)     // bash-style esc char. This is
         xlate_esc(&op, &ip); // expanded/redundant for clarity.
        else          // See 'xlate' in 'single_word()'.
         *op++ = *ip++;
       }
       // terminate the string
       *op = 0;
      
       if(! state)
        return 0;
       
       // add final quote 
       *op++ = '"';
       *op = 0;
       
       *pip = ip;
       *pop = op;
       return true;
      }
      
      // translate escaped chars from bash to chars
      // to C/C++ printable forms
      int xlate_esc(char**pop, const char** pip)
      {
       const char* ip = *pip;
       
       if(*ip != 0x5c)
        return false;
       
       char*op = *pop;
       
       switch(ip[1])
       {
        // the following are actually translated
        case 'n':
        {
         *op = '\n'; 
         break;
        }
        case 't':
        {
         *op = '\t'; 
         break;
        }
        default:
        // copy verbatim
        {
         *op++ = *ip;
         *op = ip[1];
         break;
        }
       }
       
       // adv ip by two and op by one
       ip += 2;
       op++;
       
       *pip = ip;
       *pop = op;
       return 1; 
      }
      
      
      int usage(int errcode)
      {
       const char* usage_str =
        "\n"
        "Usage: parse-this \"$list\"\n"
        " \n"
        " where $list is a list of args on separate lines such as\n"
        " \n"
        "  list=\"\n"
        "  1\n"
        "  start\n"
        "  This is your Captain speaking.\\nPlease man the lifeboats.\n"
        "  1000\n"
        "  \"\n"
        " This is a parser for a test ProgressBar daemon that will try\n"
        " to iron out jumpy displays, and maybe offer reasonable \n"
        " expectations for completion time.\n"
        " \n"
        ;
      
       printf("%s", usage_str);
       return errcode;
      }
      The usage note can by copy/pasted to the terminal and then
      Code:
      parse-this "$list"
      will produce the proper output.

      Comment


        #4
        Re: Creating a ProgressBar launcher

        Fighting with QT4 to try to compile the files from the original sources. I want some of the extra demos and stuff and I'd also like to see all the sources. Third try wasn't the charm, though. :-( Still have something wrong, but I want to share this which is... well...

        Here's the queue for the daemon in a multi-thread test app.

        Features:
        [x] Implementation of multi-thread app.
        [x] Implementation of a mutex.
        [x] FIFO (file) reading into a recycling queue and writing from the queue.
        [x] Using usleep to pass control to the operating system, i.e., other thread(s).

        This is (very likely) the queue we will use to talk to the ProgressBar daemon, but the test code itself should be generic enough to be of interest to anyone struggling with threads and mutexes. This code is somewhat optimized, but could be made safer by adding accessors for the input_queue variables by wrapping them in 'busy' checks and lock/unlock pairs.

        Here (below) is the last of ten data sets sent from thread 1 to thread 2 though a fifo and back to thread 1 through the queue.

        1. We send strings one char at a time in the first thread and terminate the packet with a null char. This simulates sending a command to the daemon.

        2. We read the fifo in the second thread and queue each char as it comes in, again, one at a time, recycling the queue when we start running out of queue space; an unrealistic 32 byte queue plus an overflow allowance for contiguous data so we can see it recyling. This simulates the daemon receiving data in its receiver thread.

        3. Back in the first thread we simulate the daemon's writing thread by reading the data queued from the fifo and relaying the data received, but to stdout. (At this time we are not sending anything to the progress bar yet.)

        Thread 1 starting Packet: 10
        Thread 1 Sending a
        Thread 1 Sending b
        Thread 1 Sending c
        Thread 1 Sending d
        Thread 1 Sending e
        Thread 1 Sending f
        Thread 1 Sending g
        Thread 1 Terminating Packet
        Thread 2 Queued p
        Thread 2 Queued a
        Thread 2 Queued c
        Thread 2 Queued k
        Thread 2 Queued e
        Thread 2 Queued t
        Thread 2 Queued
        Thread 2 Queued 1
        Thread 2 Queued 0
        Thread 2 Queued :
        Thread 2 Queued
        Thread 2 Queued a
        Thread 2 Queued b
        Thread 2 Queued c
        Thread 2 Queued d
        Thread 2 Queued e
        Thread 2 Queued f
        Thread 2 Queued g
        *** Thread 2 Cycling queue

        Thread 1 Reading Queue:
        packet 10: abcdefg

        Thread 2 Queued <
        Thread 2 Queued E
        Thread 2 Queued O
        Thread 2 Queued F
        Thread 2 Queued >

        We can see thread 2 recycling the buffer, which it does several times in this test. Also, see the code for how to hand off to the other thread to let it catch up or get ahead when necessary, without hanging your system.

        file: thread-test.cpp
        Code:
        // All the files are in this one C++ source to eliminate linking
        
        // Compile: g++ thread-test.cpp -lpthread -o thread-test
        // Link: n/a
        
        // buffer-thread.h -----------------------------------------
        #ifndef buffer_thread_h
        #define buffer_thread_h
        
        #include <pthread.h>
        extern pthread_t reader_thread;
        extern pthread_attr_t reader_attr;
        extern pthread_mutex_t reader_mutex;
        
        typedef void* thread_runner_t(void*);
        
        // sets up user supplied thread function 
        int start_thread(thread_runner_t* thread_runner, 
                      void* param);
        #endif // buffer_thread_h
        
        // input-queue.h -----------------------------------------
        #ifndef input_queue_h
        #define input_queue_h
        
        // Allowance for continuous data up to this length
        #define INPUT_QUEUE_OV_BUFFER 4096
        
        #warning for TEST ONLY. Should be 8K to a meg or more for a real app.
        #define INPUT_QUEUE_MIN (32)
        
        ///////////////////////
        // protected variables
        
        extern char _input_queue[INPUT_QUEUE_MIN + INPUT_QUEUE_OV_BUFFER];
        extern char* _input_queue_head;
        extern char* _input_queue_tail;
        extern const char* _input_queue_threshold;
        
        // protected methods
        void input_queue_lock();
        void input_queue_unlock();
        int input_queue_trylock();
        
        ///////////////////////
        // public accessors
        int input_queue_busy();
        void input_queue_lock();
        void input_queue_reset();
        
        #endif // input_queue_h
         
        // utils.h - excerpts -----------------------------------------
        #ifndef utils_h
        #define utils_h
        
        // write a file, possibly executable, from internal data. If
        // path is null, fname must be the full path to the file.
        int write_file(const char* path, const char* fname, char* data, int len, int exec);
        
        
        // translate "." "~" and NULL for file read/write.
        const char*translate_path(const char* path);
        #endif // utils_h
        
        
        // main.cpp -----------------------------------------
        ////////////////////////////////////////////////////////////////
        //        The main test program.
        ////////////////////////////////////////////////////////////////
        
        #include <stdio.h>  // sprintf()
        #include <string.h>  // strlen()
        #include <unistd.h>  // usleep()
        #include <sys/stat.h> // mkfifo()
        #include <stdlib.h>  // getenv()
        
        //#include "input-queue.h"
        //#include "buffer-thread.h"
        //#include "utils-part.h"
        
        void dbg(){} // bp for kdbg that won't move as sources are edited
        
        char channel_address[256];
        
        
        void tst_create_fifo();
        void tst_delete_fifo();
        void tst_init_fifo_reader();
        int tst_run_fifo_test();
        int fifo_write_str(int serial, const char* sending);
        int queue_read_str();
        
        int main(int argc, char** argv)
        {
         // tmp
         dbg();
         sprintf(channel_address, "%s/%s", getenv("PWD"), "test-fifo");
         tst_create_fifo();
         tst_init_fifo_reader();
         
         return tst_run_fifo_test();
        }
        
        // BEGIN PROTECTED SECTION
        
        #define input_queue_tail _input_queue_tail 
        #define input_queue_head _input_queue_head
        #define input_queue_limit _input_queue_threshold
        #define input_queue _input_queue
        
        int tst_run_fifo_test()
        {
         // write ten files, then read ten files
         int err;
         char data[32];
         int count;
         int nread;
         int packet_n;
         
         // timeout for 200 ms for other thread to finish connecting.
         usleep(200000);
         
         const char* sending = "abcdefg";
         int pkt_id = 1;
         for(int lp = 0; lp < 10; lp++, pkt_id++)
         {
          
          err = fifo_write_str(pkt_id, sending);
          
          // be nice, let fifo_reader get ahead.
          while(input_queue_head == input_queue_tail)
           usleep(100);
          
          err = queue_read_str();
          
         }
         
         // signal end of transmission so other thread doesn't hang
         FILE* fp = fopen(channel_address, "w");
         sending = "<EOF>";
         for(int i = 0; i < strlen(sending) + 1; i++)
         {
          int chr = sending[i];
          fwrite(&chr, 1, 1, fp);
         }
         fclose(fp);
         
         // continue reading until queue empty
         while(1)
         {
          int done;
          input_queue_lock();
          if(input_queue_head == input_queue_tail)
           done = 1;
          input_queue_unlock();
          if(done)
           break;
          
          queue_read_str();
         }
         
         // wait for other thread to finish, if req'd.
         usleep(100000);
         
         tst_delete_fifo();
        }
        
        
        int queue_read_str()
        {
         input_queue_lock();
         while(input_queue_head != input_queue_tail)
         {
          printf("\nThread 1 Reading Queue:\n%s\n\n", input_queue_tail);
          input_queue_tail += strlen(input_queue_tail) + 1;
          if(input_queue_tail > _input_queue_threshold)
           input_queue_tail = input_queue;
         }
         input_queue_unlock();
        }
        
        
        ////////////////////////////////////////////////////
        int fifo_write_str(int serial, const char* sending)
        {
         FILE* fp = fopen(channel_address, "w");
         if(!fp)
          exit(1); // abort
         for(int i = 0; i < strlen(sending)+1; i ++)
         {   
          char chr;
          if(i == 0)
          {
           printf("Thread 1 starting Packet: %.2d\n", serial);
           fprintf(fp, "packet %.2d: ", serial);
          }
          chr = sending[i];
          if(chr)
           printf("Thread 1 Sending %c\n", chr);
          else
          {
           printf("Thread 1 Terminating Packet\n");
          }
          fwrite(&chr, 1, 1, fp);
          if(! chr)
           break;
         }
         fclose(fp);
        }
        
        /////////////////////////////////////////////////
        
        void* fifo_reader(void* param)
        {
         const char* fifo_name = (const char*)param;
         FILE* fp = 0; 
         
         // wait for fifo to appear
         while(!fp)
         {
          usleep(100000);
          fp = fopen(channel_address, "r");
         }
         
         
         // local pointer for head pointer
         char* head;
         input_queue_lock();
         head = input_queue_head; 
         input_queue_unlock();
         
         char* start = head;
         int stop = 0;
         
         int loop = 0;
         
         while(! stop)
         {
          char chr;
          int n = 0;
          
          // wait for a char
          while(!n)
          {
           // wait for a char
           n = fread(&chr, 1, 1, fp);
           if(!n)
            usleep(100);
          }
          
          if(chr == '<')
           start = head;
          if(chr == '>')
          {
           if(memcmp(start, "<EOF>", 5) == 0)
            stop = 1;
          }
          
          *head++ = chr;
          
          if(chr)
           printf("Thread 2 Queued %c\n", chr);
          else // null char
          {
           // write chr and recycle if past limit
           input_queue_lock();
           if(head > input_queue_limit)
           {
            printf("*** Thread 2 Cycling queue\n");
            head = input_queue;
           }
           input_queue_head = head;
           input_queue_unlock();
          }
          
          // don't clobber unread data, wait for other thread to
          // catch up.
          input_queue_lock();
          while(head == input_queue_tail)
          {
           input_queue_unlock();
           usleep(100);
           input_queue_lock();
          }
          input_queue_unlock();
         }
         // one second pause to let other thread finish, if req'd.
         usleep(1000000);
         fclose(fp);
         return 0;
        }
        
        #undef input_queue_tail
        #undef input_queue_head 
        #undef input_queue_limit 
        #undef input_queue 
        // END PROTECTED SECTION
        
        
        void tst_create_fifo()
        {
         mkfifo(channel_address, 0664);
        }
        
        void tst_delete_fifo()
        {
         remove(channel_address);
        }
        
        
        void tst_init_fifo_reader()
        {
         int err;
         err = start_thread(fifo_reader, (void*) channel_address);
         if(err)
          exit(1);
         
        }
        
        ////////////////////////////////////////////////////////////////
        //    Included 'lib' funcs inline for convenience
        ////////////////////////////////////////////////////////////////
        
        // buffer-thread.cpp -------------------------------------
        
        #include <pthread.h>
        //#include "buffer-thread.h"
        
        pthread_t reader_thread;
        pthread_attr_t reader_attr;
        pthread_mutex_t reader_mutex;
        
        
        // int start_reader_thread(thread_runner_t* thread_runner, void* param)
        // returns errorcode
        int start_thread(thread_runner_t* thread_runner, 
                      void* param)
        {
         if(pthread_mutex_init(&reader_mutex, NULL))
          return 1;
         if(pthread_attr_init(&reader_attr))
          return 1;
         return pthread_create(&reader_thread, &reader_attr, thread_runner, param);
        }
        
        // input-queue.cpp - a non-object oriented thread for handling an
        // input queue. See notes in the header file.
        
        //#include "buffer-thread.h"
        
        // access protected members
        //#include "input-queue.h"
        
        ///////////////////////////////////////////////////////////////
        // private
        
        // true if busy or locked
        static bool input_queue_status;
        
        ///////////////////////////////////////////////////////////////
        // protected variables
        
        char _input_queue[INPUT_QUEUE_MIN + INPUT_QUEUE_OV_BUFFER];
        char* _input_queue_head = _input_queue;
        char* _input_queue_tail = _input_queue;
        const char* _input_queue_threshold = 
          _input_queue + INPUT_QUEUE_MIN;
        
        ///////////////////////////////////////////////////////////////
        // protected methods
        void input_queue_lock()
        {
         pthread_mutex_lock(&reader_mutex);
         input_queue_status = 1;
         pthread_mutex_unlock(&reader_mutex);
        }
        
        // wait until we can unlock, mostly assures we block
        // until the lock is released.
        void input_queue_unlock()
        {
         pthread_mutex_lock(&reader_mutex);
         input_queue_status = 0;
         pthread_mutex_unlock(&reader_mutex);
        }
        
        // returns true if we got the lock
        int input_queue_trylock()
        {
         int was_busy;
         pthread_mutex_lock(&reader_mutex);
         was_busy = input_queue_status;
         if(! was_busy)
          input_queue_status = 1;
         pthread_mutex_unlock(&reader_mutex);
         // if it wasn't busy, it is now and it's ours.
         return ! was_busy;
        }
        
        
        ///////////////////////////////////////////////////////////////
        // public accessors
        
        int input_queue_busy()
        {
         // input_queue_lock();
         pthread_mutex_lock(&reader_mutex);
         int b = input_queue_status;
         pthread_mutex_unlock(&reader_mutex);
         return b;
        }
        
        
        void input_queue_set_busy()
        {
         pthread_mutex_lock(&reader_mutex);
         input_queue_status = 1;
         pthread_mutex_unlock(&reader_mutex);
        }
        
        void input_queue_reset_busy()
        {
         pthread_mutex_lock(&reader_mutex);
         input_queue_status = 0;
         pthread_mutex_unlock(&reader_mutex);
        }
        
        // utils.cpp -- excerpts -------------------------------------
        
        
        // some commonly used c headers
        #include <stdio.h>  // printf, sprintf, etc.
        #include <stdlib.h>  // exit()
        #include <unistd.h>  // getpid() getppid()..tons of stuff
        #include <malloc.h>  // memory allocation
        #include <string.h>  // strings and block comparisons and moves
        #include <errno.h>  // error numbers
        #include <stddef.h>  // pid/ppid
        #include <stdlib.h>  // canonicalize_file_name()
        #include <ctype.h>  // char manips
        #include <sys/time.h> // gettimeofday()
        
        //#include "utils-part.h"
        //#include "parser.h"
        
        // write a file from data, optionally executable. If path is null,
        // fname must be the full path to the file. If path is '.' uses
        // PWD. if path is '~', uses HOME
        int write_file(const char* path, const char* fname, char* data, int len, int exec) 
        {
         path = translate_path(path);
         
         char fullname[256];
         char cmd[256];
         sprintf(fullname, "%s/%s", path, fname);
         FILE* fp = fopen(fullname, "w");
         if(!fp) return 1;
         fwrite(data, 1, len, fp);
         fclose(fp);
         if(exec) 
         {
          sprintf(cmd, "chmod +x %s", fullname);
          return system(cmd);
         }
         else
          return 0;
        }
                 
                    
        const char*translate_path(const char* path)
        {
         const char* path_out;
         if(path == 0)
          return "";
         else if(strcmp(path,".") == 0)
          return getenv("PWD");
         else if(strcmp(path,"~") == 0)
          return getenv("HOME");
         else return path;
        } 
        
        
        void strip_newline(char* s)
        {
         int len = strlen(s);
         if(len)
         {
          len--;
          if(s[len] == '\n')
           s[len] = 0;
         }
        }
        The compilation clues are at the top of the source code.

        If you insert unlocked usleeps() various places in the code you can get the two threads to send their data more or less simultaneously (all mixed together) and the data is still reliable.

        That's just for fun. :-) What we have here is the more practical implementation.

        PS. Kubuntu 11.10 behaves better in the context of C/C++ programming and sandboxed installations than 12.4.

        Comment

        Working...
        X