source: trunk/packages/vizservers/pymolproxy/pymolproxy2.c @ 3377

Last change on this file since 3377 was 3377, checked in by gah, 10 years ago

rework of stats log file.

File size: 63.7 KB
Line 
1
2/*
3 * ----------------------------------------------------------------------
4 * proxypymol2.c
5 *
6 *      This module creates the Tcl interface to the pymol server.  It acts as
7 *      a go-between establishing communication between a molvisviewer widget
8 *      and the pymol server. The communication protocol from the molvisviewer
9 *      widget is the Tcl language.  Commands are then relayed to the pymol
10 *      server.  Responses from the pymol server are translated into Tcl
11 *      commands and send to the molvisviewer widget. For example, resulting
12 *      image rendered offscreen is returned as ppm-formatted image data.
13 *
14 *  Copyright (c) 2004-2012  HUBzero Foundation, LLC
15 *
16 *  See the file "license.terms" for information on usage and
17 *  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
18 * ======================================================================
19 */
20
21/*
22 * Notes:   The proxy should not maintain any state information from the
23 *          client, other that what it needs for event (rotate, pan, zoom,
24 *          atom scale, bond thickness, etc.) compression.  This is because
25 *          the connection is periodically broken (timeout, error, etc.).
26 *          It's the responsibility of the client (molvisviewer) to restore
27 *          the settings of the previous view.  The proxy is just a relay
28 *          between the client and the pymol server.
29 */
30/*
31 *   +--------------+          +------------+          +----------+
32 *   |              | (stdin)  |            |  (stdin) |          |
33 *   |              |--------->|            |--------->|          |
34 *   | molvisviewer |          | pymolproxy |          | pymol    |
35 *   |  (client)    |          |            |          | (server) |(stderr)
36 *   |              | (stdout) |            | (stdout) |          |-------> file
37 *   |              |<---------|            |<---------|          |
38 *   +--------------+          +------------+          +----------+
39 *
40 * We're using a simple 2 thread setup: one for read client requests and
41 * relaying them to the pymol server, another reading pymol server output,
42 * and sending images by to the client.  The reason is because the client
43 * blocks on writes.  While it is sending requests or data, the proxy
44 * must be available to accept it.  Likewise, the proxy buffers images in a
45 * linked list when the client isn't ready to receive them.
46 *
47 * Reader thread:
48 * The communication between the pymol server and proxy is asynchronous. 
49 * The proxy translates commands from the client and sends them to the
50 * server without waiting for a response.  It watches the server's
51 * stdout in a separate thread snooping for image data. 
52 *
53 * Writer thread:
54 * The communication between the client and the proxy is also asynchronous. 
55 * The client commands are read when they become available on the socket. 
56 * The proxy writes images to the client when it can, otherwise it stores
57 * them in a list in memory.  This should prevent deadlocks from occuring:
58 * the client sends a request, while the proxy is writing an image.
59 */
60
61#include <assert.h>
62#include <ctype.h>
63#include <errno.h>
64#include <fcntl.h>
65#include <getopt.h>
66#include <poll.h>
67#include <stdio.h>
68#include <stdlib.h>
69#include <string.h>
70#include <sys/stat.h>
71#include <sys/time.h>
72#include <sys/times.h>
73#include <sys/types.h>
74#include <sys/wait.h>
75#include <time.h>
76#include <syslog.h>
77#include <unistd.h>
78#include <tcl.h>
79#include <pthread.h>
80#include <md5.h>
81
82#undef INLINE
83#ifdef __GNUC__
84#  define INLINE __inline__
85#else
86#  define INLINE
87#endif
88
89#define FALSE 0
90#define TRUE  1
91
92static int debug = FALSE;
93static char stderrFile[200];
94static FILE *fdebug;
95static FILE *frecord;
96static int recording = FALSE;
97static int pymolIsAlive = TRUE;
98static int statsFile = -1;
99
100#define WANT_DEBUG      0
101#define READ_DEBUG      0
102#define WRITE_DEBUG     0
103#define EXEC_DEBUG      0
104
105#define FORCE_UPDATE            (1<<0)
106#define CAN_UPDATE              (1<<1)
107#define INVALIDATE_CACHE        (1<<3)
108#define ATOM_SCALE_PENDING      (1<<4)
109#define STICK_RADIUS_PENDING    (1<<5)
110#define ROTATE_PENDING          (1<<6)
111#define PAN_PENDING             (1<<7)
112#define ZOOM_PENDING            (1<<8)
113#define UPDATE_PENDING          (1<<9)
114#define VIEWPORT_PENDING        (1<<10)
115
116#define IO_TIMEOUT (30000)
117#define STATSDIR        "/var/tmp/visservers"
118#define CVT2SECS(x)  ((double)(x).tv_sec) + ((double)(x).tv_usec * 1.0e-6)
119
120typedef struct {
121    pid_t pid;                          /* Child process. */
122    size_t numFrames;                   /* # of frames sent to client. */
123    size_t numBytes;                    /* # of bytes for all frames. */
124    size_t numCommands;                 /* # of commands executed */
125    double cmdTime;                     /* Elapsed time spend executing
126                                         * commands. */
127    struct timeval start;               /* Start of elapsed time. */
128} Stats;
129
130static Stats stats;
131
132typedef struct Image {
133    struct Image *nextPtr;              /* Next image in chain of images. The
134                                         * list is ordered by the most
135                                         * recently received image from the
136                                         * pymol server to the least. */
137    struct Image *prevPtr;              /* Previous image in chain of
138                                         * images. The list is ordered by the
139                                         * most recently received image from
140                                         * the pymol server to the least. */
141    int id;
142    ssize_t numWritten;                 /* Number of bytes of image data
143                                         * already delivered.*/
144    size_t bytesLeft;                   /* Number of bytes of image data left
145                                         * to delivered to the client. */
146    unsigned char data[1];              /* Start of image data. We allocate
147                                         * the size of the Image structure
148                                         * plus the size of the image data. */
149} Image;
150
151#define BUFFER_SIZE             4096
152
153typedef struct {
154    Image *headPtr, *tailPtr;           /* List of images to be delivered to
155                                         * the client.  The most recent images
156                                         * are in the front of the list. */
157} ImageList;
158
159typedef struct {
160    const char *ident;
161    unsigned char *bytes;
162    size_t fill;
163    size_t mark;
164    size_t newline;
165    size_t bufferSize;
166    int lastStatus;
167    int fd;
168} ReadBuffer;
169
170#define BUFFER_OK                0
171#define BUFFER_ERROR            -1
172#define BUFFER_CONTINUE         -2
173#define BUFFER_EOF              -3
174
175typedef struct {
176    Tcl_Interp *interp;
177    unsigned int flags;                 /* Various flags. */
178    int sin, sout;                      /* Server file descriptors. */
179    int cin, cout;                      /* Client file descriptors. */
180    ReadBuffer client;                  /* Read buffer for client input. */
181    ReadBuffer server;                  /* Read buffer for server output. */
182    int frame;
183    int rockOffset;
184    int cacheId;
185    int error;
186    int status;
187    int width, height;                  /* Size of viewport. */
188    float xAngle, yAngle, zAngle;       /* Euler angles of pending
189                                         * rotation.  */
190    float sphereScale;                  /* Atom scale of pending re-scale. */
191    float stickRadius;                  /* Bond thickness of pending
192                                         * re-scale. */
193    float zoom;
194    float xPan, yPan;
195    pid_t pid;
196} PymolProxy;
197
198#if WANT_DEBUG
199#define DEBUG(...)      if (debug) PrintToLog(__VA_ARGS__)
200#else
201#define DEBUG(...)
202#endif
203
204#define ERROR(...)      SysLog(LOG_ERR, __FILE__, __LINE__, __VA_ARGS__)
205#define TRACE(...)      SysLog(LOG_DEBUG, __FILE__, __LINE__, __VA_ARGS__)
206#define WARN(...)       SysLog(LOG_WARNING, __FILE__, __LINE__, __VA_ARGS__)
207#define INFO(...)       SysLog(LOG_INFO, __FILE__, __LINE__, __VA_ARGS__)
208
209static const char *syslogLevels[] = {
210    "emergency",                        /* System is unusable */
211    "alert",                            /* Action must be taken immediately */
212    "critical",                         /* Critical conditions */
213    "error",                            /* Error conditions */
214    "warning",                          /* Warning conditions */
215    "notice",                           /* Normal but significant condition */
216    "info",                             /* Informational */
217    "debug",                            /* Debug-level messages */
218};
219
220#if WANT_DEBUG
221static void
222PrintToLog TCL_VARARGS_DEF(const char *, arg1)
223{
224    const char *format;
225    va_list args;
226   
227    format = TCL_VARARGS_START(const char *, arg1, args);
228    fprintf(fdebug, "pymolproxy: ");
229    vfprintf(fdebug, format, args);
230    fprintf(fdebug, "\n");
231    fflush(fdebug);
232}
233#endif
234
235void
236SysLog(int priority, const char *path, int lineNum, const char* fmt, ...)
237{
238#define MSG_LEN (2047)
239    char message[MSG_LEN+1];
240    const char *s;
241    int length;
242    va_list lst;
243
244    va_start(lst, fmt);
245    s = strrchr(path, '/');
246    if (s == NULL) {
247        s = path;
248    } else {
249        s++;
250    }
251    length = snprintf(message, MSG_LEN, "pymolproxy2 (%d) %s: %s:%d ",
252        getpid(), syslogLevels[priority],  s, lineNum);
253    length += vsnprintf(message + length, MSG_LEN - length, fmt, lst);
254    message[MSG_LEN] = '\0';
255    if (debug) {
256        DEBUG("%s\n", message);
257    } else {
258        syslog(priority, message, length);
259    }
260}
261
262static void
263Record TCL_VARARGS_DEF(const char *, arg1)
264{
265    const char *format;
266    va_list args;
267   
268    format = TCL_VARARGS_START(const char *, arg1, args);
269    vfprintf(frecord, format, args);
270    fflush(frecord);
271}
272
273static int
274SendToPymol(PymolProxy *proxyPtr, const char *format, ...)
275{
276    va_list ap;
277    char buffer[BUFSIZ];
278    int result;
279    ssize_t numWritten;
280    size_t length;
281
282    if (proxyPtr->error) {
283        return TCL_ERROR;
284    }
285   
286    va_start(ap, format);
287    result = vsnprintf(buffer, BUFSIZ-1, format, ap);
288    va_end(ap);
289   
290#ifdef EXEC_DEBUG
291    DEBUG("to-pymol>(%s) code=%d", buffer, result);
292#endif
293    if (recording) {
294        Record("%s\n", buffer);
295    }
296   
297    /* Write the command out to the server. */
298    length = strlen(buffer);
299    numWritten = write(proxyPtr->sin, buffer, length);
300    if (numWritten != length) {
301        ERROR("short write to pymol (wrote=%d, should have been %d): %s",
302              numWritten, length, strerror(errno));
303    }
304    proxyPtr->status = result;
305    return  proxyPtr->status;
306}
307
308
309
310/* -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
311/*
312 * Copyright (C) 2011, Purdue Research Foundation
313 *
314 * Author: George A. Howlett <gah@purdue.edu>
315 */
316
317static void
318FlushReadBuffer(ReadBuffer *bp)
319{
320    bp->fill = bp->mark = 0;
321    bp->newline = 0;
322}
323
324/**
325 * \param[in] fd File descriptor to read
326 * \param[in] bufferSize Block size to use in internal buffer
327 */
328
329static void
330InitReadBuffer(ReadBuffer *bp, const char *id, int fd, size_t bufferSize)
331{
332    bp->ident = id;
333    bp->bufferSize = bufferSize;
334    bp->fd = fd;
335    bp->lastStatus = BUFFER_OK;
336    bp->bytes = malloc(bufferSize);
337    FlushReadBuffer(bp);
338}
339
340/**
341 * \brief Checks if a new line is currently in the buffer.
342 *
343 * \return the index of the character past the new line.
344 */
345static size_t
346NextLine(ReadBuffer *bp)
347{
348    /* Check for a newline in the current buffer. */
349
350    if (bp->newline > 0) {
351        return bp->newline;
352    } else {
353        unsigned char *p;
354        size_t newline;
355
356        p = (unsigned char *)memchr(bp->bytes + bp->mark, '\n',
357                                    bp->fill - bp->mark);
358        if (p == NULL) {
359            newline = 0;
360        } else {
361            newline = (p - bp->bytes + 1);
362        }
363        bp->newline = newline;
364        return newline;
365    }
366}
367
368/** 
369 * \brief Fills the buffer with available data.
370 *
371 * Any existing data in the buffer is moved to the front of the buffer,
372 * then the channel is read to fill the rest of the buffer.
373 *
374 * \return If an error occur when reading the channel, then ERROR is
375 * returned. ENDFILE is returned on EOF.  If the buffer can't be filled,
376 * then CONTINUE is returned.
377 */
378static int
379FillReadBuffer(ReadBuffer *bp)
380{
381    ssize_t numRead;
382    size_t bytesLeft;
383
384#if READ_DEBUG
385    DEBUG("Enter FillReadBuffer for %s: mark=%lu fill=%lu",
386          bp->ident, bp->mark, bp->fill);
387#endif
388    if (bp->mark >= bp->fill) {
389        FlushReadBuffer(bp);    /* Fully consumed buffer */
390    }
391    if (bp->mark > 0) {
392        /* Some data has been consumed. Move the unconsumed data to the front
393         * of the buffer. */
394#if READ_DEBUG
395        DEBUG("memmove %lu bytes", bp->fill - bp->mark);
396#endif
397        memmove(bp->bytes, bp->bytes + bp->mark,
398                bp->fill - bp->mark);
399        bp->fill -= bp->mark;
400        bp->mark = 0;
401    }
402
403    bytesLeft = bp->bufferSize - bp->fill;
404#if READ_DEBUG
405    DEBUG("going to read %lu bytes", bytesLeft);
406#endif
407    numRead = read(bp->fd, bp->bytes + bp->fill, bytesLeft);
408    if (numRead == 0) {
409        /* EOF */
410#if READ_DEBUG
411        DEBUG("EOF found reading %s buffer (fill=%d): ",
412              bp->ident, bp->fill);
413#endif
414        return BUFFER_EOF;
415    }
416    if (numRead < 0) {
417        if (errno != EAGAIN) {
418            ERROR("error reading %s buffer: %s", bp->ident, strerror(errno));
419            return BUFFER_ERROR;
420        }
421#if READ_DEBUG
422        DEBUG("Short read for buffer");
423#endif
424        return BUFFER_CONTINUE;
425    }
426    bp->fill += numRead;
427#if READ_DEBUG
428    DEBUG("Read %lu bytes", numRead);
429#endif
430    return ((size_t)numRead == bytesLeft) ? BUFFER_OK : BUFFER_CONTINUE;
431}
432
433/**
434 * \brief Read the requested number of bytes from the buffer.
435
436 * Fails if the requested number of bytes are not immediately
437 * available. Never should be short.
438 */
439static int
440ReadFollowingData(ReadBuffer *bp, unsigned char *out, size_t numBytes)
441{
442#if READ_DEBUG
443    DEBUG("Enter ReadFollowingData %s", bp->ident);
444#endif
445    while (numBytes > 0) {
446        size_t bytesLeft;
447
448        bytesLeft = bp->fill - bp->mark;
449        if (bytesLeft > 0) {
450            int size;
451
452            /* Pull bytes out of the buffer, updating the mark. */
453            size = (bytesLeft >  numBytes) ? numBytes : bytesLeft;
454            memcpy(out, bp->bytes + bp->mark, size);
455            bp->mark += size;
456            numBytes -= size;
457            out += size;
458        }
459        if (numBytes == 0) {
460            /* Received requested # bytes. */
461            return BUFFER_OK;
462        }
463        /* Didn't get enough bytes, need to read some more. */
464        bp->lastStatus = FillReadBuffer(bp);
465        if ((bp->lastStatus == BUFFER_ERROR) ||
466            (bp->lastStatus == BUFFER_EOF)) {
467            return bp->lastStatus;
468        }
469    }
470    return BUFFER_OK;
471}
472
473/**
474 * \brief Returns the next available line (terminated by a newline)
475 *
476 * If insufficient data is in the buffer, then the channel is
477 * read for more data.  If reading the channel results in a
478 * short read, CONTINUE is returned and *numBytesPtr is set to 0.
479 */
480static int
481GetLine(ReadBuffer *bp, size_t *numBytesPtr, const char **bytesPtr)
482{
483#if READ_DEBUG
484    DEBUG("Enter GetLine");
485#endif
486    *numBytesPtr = 0;
487    *bytesPtr = NULL;
488
489    bp->lastStatus = BUFFER_OK;
490    for (;;) {
491        size_t newline;
492
493        newline = NextLine(bp);
494        if (newline > 0) {
495            /* Start of the line. */
496            *bytesPtr = (const char *)(bp->bytes + bp->mark);
497            /* Number of bytes in the line. */
498            *numBytesPtr = newline - bp->mark;
499            bp->mark = newline;
500            bp->newline = 0;
501            return BUFFER_OK;
502        }
503        /* Couldn't find a newline, so it may be that we need to read some
504         * more. Check first that last read wasn't a short read. */
505        if (bp->lastStatus == BUFFER_CONTINUE) {
506            /* No complete line just yet. */
507            return BUFFER_CONTINUE;
508        }
509        /* Try to add more data to the buffer. */
510        bp->lastStatus = FillReadBuffer(bp);
511        if (bp->lastStatus == BUFFER_ERROR ||
512            bp->lastStatus == BUFFER_EOF) {
513            return bp->lastStatus;
514        }
515        /* OK or CONTINUE */
516    }
517    return BUFFER_CONTINUE;
518}
519
520static int
521IsLineAvailable(ReadBuffer *bp)
522{
523    return (NextLine(bp) > 0);
524}
525
526static int
527WaitForNextLine(ReadBuffer *bp, struct timeval *tvPtr)
528{
529    fd_set readFds;
530    int n;
531
532    if (IsLineAvailable(bp)) {
533        return 1;
534    }
535    FD_ZERO(&readFds);
536    FD_SET(bp->fd, &readFds);
537    n = select(bp->fd+1, &readFds, NULL, NULL, tvPtr);
538    return (n > 0);
539}
540
541INLINE static void
542clear_error(PymolProxy *proxyPtr)
543{
544    proxyPtr->error = 0;
545    proxyPtr->status = TCL_OK;
546}
547
548
549#define STATSDIR        "/var/tmp/visservers"
550
551static int
552SplitPath(const char *path, int *argcPtr, char ***argvPtr)
553{
554    char **array;
555    int count;
556    char *p;
557    char *s;
558    size_t addrsize;
559
560    count = 0;
561    for (p = strchr((char *)path, '/'); p != NULL; p = strchr(p+1, '/')) {
562        count++;
563    }
564    addrsize = (count + 1) * sizeof(char *);
565    array = (char **)malloc(addrsize + strlen(path) + 1);
566    s = (char *)array + addrsize;
567    strcpy(s, path);
568   
569    count = 0;
570    for (p = strtok(s, "/"); p != NULL; p = strtok(NULL, "/")) {
571        array[count++] = p;
572    }
573    *argcPtr = count;
574    *argvPtr = array;
575    return count;
576}
577
578static int
579GetStatsFile(const char *string)
580{
581    Tcl_DString ds;
582    int i;
583    char fileName[33];
584    const char *path;
585    int length;
586    char pidstr[200];
587    md5_state_t state;
588    md5_byte_t digest[16];
589
590    if (string == NULL) {
591        return statsFile;
592    }
593    /* By itself the client's key/value pairs aren't unique.  Add in the
594     * process id of this render server. */
595    Tcl_DStringInit(&ds);
596    Tcl_DStringAppend(&ds, string, -1);
597    Tcl_DStringAppendElement(&ds, "pid", 3);
598    sprintf(pidstr, "%d", getpid());
599    Tcl_DStringAppendElement(&ds, pidstr, -1);
600
601    /* Create a md5 hash of the key/value pairs and use it as the file name. */
602    string = Tcl_DStringValue(&ds);
603    length = strlen(string);
604    md5_init(&state);
605    md5_append(&state, (const md5_byte_t *)string, length);
606    md5_finish(&state, digest);
607    for (i = 0; i < 16; i++) {
608        sprintf(fileName + i * 2, "%02x", digest[i]);
609    }
610    Tcl_DStringLength(&ds, 0);
611    Tcl_DStringAppend(&ds, STATSDIR, -1);
612    Tcl_DStringAppend(&ds, "/", 1);
613    Tcl_DStringAppend(&ds, fileName, 32);
614    path = Tcl_DStringValue(&ds);
615
616    statsFile = open(path, O_EXCL | O_CREAT | O_WRONLY, 0600);
617    Tcl_DStringFree(&ds);
618    if (statsFile < 0) {
619        ERROR("can't open \"%s\": %s", fileName, strerror(errno));
620        return -1;
621    }
622    return statsFile;
623}
624
625static int
626WriteToStatsFile(int f, const char *s, size_t length)
627{
628
629    if (f >= 0) {
630        ssize_t numWritten;
631
632        numWritten = write(f, s, length);
633        if (numWritten == (ssize_t)length) {
634            close(dup(f));
635        }
636    }
637    return 0;
638}
639
640static int
641ServerStats(int code)
642{
643    double start, finish;
644    char buf[BUFSIZ];
645    Tcl_DString ds;
646    int result;
647
648    {
649        struct timeval tv;
650
651        /* Get ending time.  */
652        gettimeofday(&tv, NULL);
653        finish = CVT2SECS(tv);
654        tv = stats.start;
655        start = CVT2SECS(tv);
656    }
657    /*
658     * Session information:
659     *   - Name of render server
660     *   - Process ID
661     *   - Hostname where server is running
662     *   - Start date of session
663     *   - Start date of session in seconds
664     *   - Number of frames returned
665     *   - Number of bytes total returned (in frames)
666     *   - Number of commands received
667     *   - Total elapsed time of all commands
668     *   - Total elapsed time of session
669     *   - Exit code of vizserver
670     *   - User time
671     *   - System time
672     *   - User time of children
673     *   - System time of children
674     */
675
676    Tcl_DStringInit(&ds);
677   
678    Tcl_DStringAppendElement(&ds, "render_stop");
679    /* renderer */
680    Tcl_DStringAppendElement(&ds, "renderer");
681    Tcl_DStringAppendElement(&ds, "pymol");
682    /* pid */
683    Tcl_DStringAppendElement(&ds, "pid");
684    sprintf(buf, "%d", getpid());
685    Tcl_DStringAppendElement(&ds, buf);
686    /* host */
687    Tcl_DStringAppendElement(&ds, "host");
688    gethostname(buf, BUFSIZ-1);
689    buf[BUFSIZ-1] = '\0';
690    Tcl_DStringAppendElement(&ds, buf);
691    /* date */
692    Tcl_DStringAppendElement(&ds, "date");
693    strcpy(buf, ctime(&stats.start.tv_sec));
694    buf[strlen(buf) - 1] = '\0';
695    Tcl_DStringAppendElement(&ds, buf);
696    /* date_secs */
697    Tcl_DStringAppendElement(&ds, "date_secs");
698    sprintf(buf, "%ld", stats.start.tv_sec);
699    Tcl_DStringAppendElement(&ds, buf);
700    /* num_frames */
701    Tcl_DStringAppendElement(&ds, "num_frames");
702    sprintf(buf, "%lu", (unsigned long int)stats.numFrames);
703    Tcl_DStringAppendElement(&ds, buf);
704    /* frame_bytes */
705    Tcl_DStringAppendElement(&ds, "frame_bytes");
706    sprintf(buf, "%lu", (unsigned long int)stats.numBytes);
707    Tcl_DStringAppendElement(&ds, buf);
708    /* num_commands */
709    Tcl_DStringAppendElement(&ds, "num_commands");
710    sprintf(buf, "%lu", (unsigned long int)stats.numCommands);
711    Tcl_DStringAppendElement(&ds, buf);
712    /* cmd_time */
713    Tcl_DStringAppendElement(&ds, "cmd_time");
714    sprintf(buf, "%g", stats.cmdTime);
715    Tcl_DStringAppendElement(&ds, buf);
716    /* session_time */
717    Tcl_DStringAppendElement(&ds, "session_time");
718    sprintf(buf, "%g", finish - start);
719    Tcl_DStringAppendElement(&ds, buf);
720    /* status */
721    Tcl_DStringAppendElement(&ds, "status");
722    sprintf(buf, "%d", code);
723    Tcl_DStringAppendElement(&ds, buf);
724    {
725        long clocksPerSec = sysconf(_SC_CLK_TCK);
726        double clockRes = 1.0 / clocksPerSec;
727        struct tms tms;
728
729        memset(&tms, 0, sizeof(tms));
730        times(&tms);
731        /* utime */
732        Tcl_DStringAppendElement(&ds, "utime");
733        sprintf(buf, "%g", tms.tms_utime * clockRes);
734        Tcl_DStringAppendElement(&ds, buf);
735        /* stime */
736        Tcl_DStringAppendElement(&ds, "stime");
737        sprintf(buf, "%g", tms.tms_stime * clockRes);
738        Tcl_DStringAppendElement(&ds, buf);
739        /* cutime */
740        Tcl_DStringAppendElement(&ds, "cutime");
741        sprintf(buf, "%g", tms.tms_cutime * clockRes);
742        Tcl_DStringAppendElement(&ds, buf);
743        /* cstime */
744        Tcl_DStringAppendElement(&ds, "cstime");
745        sprintf(buf, "%g", tms.tms_cstime * clockRes);
746        Tcl_DStringAppendElement(&ds, buf);
747    }
748    Tcl_DStringAppend(&ds, "\n", -1);
749    f = GetStatsFile(NULL);
750    result = WriteToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
751    Tcl_DStringFree(&ds);
752    close(f);
753    return result;
754}
755
756
757static int
758CartoonCmd(ClientData clientData, Tcl_Interp *interp, int argc,
759           const char *argv[])
760{
761    PymolProxy *p = clientData;
762    int bool, defer, push, i;
763    const char *model;
764
765    clear_error(p);
766    defer = push = FALSE;
767    model = "all";
768    bool = FALSE;
769    for(i = 1; i < argc; i++) {
770        if (strcmp(argv[i],"-defer") == 0) {
771            defer = TRUE;
772        } else if (strcmp(argv[i],"-push") == 0) {
773            push = TRUE;
774        } else if (strcmp(argv[i],"-model") == 0) {
775            if (++i < argc) {
776                model = argv[i];
777            }
778        } else {
779            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
780                return TCL_ERROR;
781            }
782        }
783    }
784    p->flags |= INVALIDATE_CACHE;       /* cartoon */
785    if (!defer || push) {
786        p->flags |= UPDATE_PENDING;
787    }
788    if (push) {
789        p->flags |= FORCE_UPDATE;
790    }
791    if (bool) {
792        SendToPymol(p, "show cartoon,%s\n", model);
793    } else {
794        SendToPymol(p, "hide cartoon,%s\n", model);
795    }
796    return p->status;
797}
798
799static int
800CartoonTraceCmd(ClientData clientData, Tcl_Interp *interp, int argc,
801                const char *argv[])
802{
803    PymolProxy *p = clientData;
804    int bool, defer, push, i;
805    const char *model;
806
807    clear_error(p);
808    defer = push = bool = FALSE;
809    model = "all";
810    for(i = 1; i < argc; i++) {
811        if (strcmp(argv[i],"-defer") == 0) {
812            defer = TRUE;
813        } else if (strcmp(argv[i],"-push") == 0) {
814            push = TRUE;
815        } else if (strcmp(argv[i],"-model") == 0) {
816            if (++i < argc) {
817                model = argv[i];
818            }
819        } else {
820            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
821                return TCL_ERROR;
822            }
823        }
824    }
825    p->flags |= INVALIDATE_CACHE; /* cartoon_trace  */
826    if (!defer || push) {
827        p->flags |= UPDATE_PENDING;
828    }
829    if (push) {
830        p->flags |= FORCE_UPDATE;
831    }
832    SendToPymol(p, "set cartoon_trace,%d,%s\n", bool, model);
833    return p->status;
834}
835
836static int
837DisableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
838           const char *argv[])
839{
840    PymolProxy *p = clientData;
841    const char *model = "all";
842    int i, defer, push;
843
844    clear_error(p);
845    defer = push = FALSE;
846    for(i = 1; i < argc; i++) {
847        if (strcmp(argv[i], "-defer") == 0 )
848            defer = 1;
849        else if (strcmp(argv[i], "-push") == 0 )
850            push = 1;
851        else
852            model = argv[i];
853    }
854
855    p->flags |= INVALIDATE_CACHE;       /* disable */
856    if (!defer || push) {
857        p->flags |= UPDATE_PENDING;
858    }
859    if (push) {
860        p->flags |= FORCE_UPDATE;
861    }
862    SendToPymol(p, "disable %s\n", model);
863    return p->status;
864}
865
866
867static int
868EnableCmd(ClientData clientData, Tcl_Interp *interp, int argc,
869          const char *argv[])
870{
871    PymolProxy *p = clientData;
872    const char *model;
873    int i, defer, push;
874
875    clear_error(p);
876    push = defer = FALSE;
877    model = "all";
878    for(i = 1; i < argc; i++) {
879        if (strcmp(argv[i],"-defer") == 0) {
880            defer = TRUE;
881        } else if (strcmp(argv[i], "-push") == 0) {
882            push = TRUE;
883        } else {
884            model = argv[i];
885        }
886    }
887    p->flags |= INVALIDATE_CACHE; /* enable */
888    if (!defer || push) {
889        p->flags |= UPDATE_PENDING;
890    }
891    if (push) {
892        p->flags |= FORCE_UPDATE;
893    }
894    SendToPymol(p, "enable %s\n", model);
895    return p->status;
896}
897
898static int
899FrameCmd(ClientData clientData, Tcl_Interp *interp, int argc,
900         const char *argv[])
901{
902    PymolProxy *p = clientData;
903    int i, push, defer, frame;
904
905    clear_error(p);
906    frame = 0;
907    push = defer = FALSE;
908    for(i = 1; i < argc; i++) {
909        if (strcmp(argv[i],"-defer") == 0) {
910            defer = TRUE;
911        } else if (strcmp(argv[i],"-push") == 0) {
912            push = TRUE;
913        } else {
914            frame = atoi(argv[i]);
915        }
916    }
917    if (!defer || push) {
918        p->flags |= UPDATE_PENDING;
919    }
920    if (push) {
921        p->flags |= FORCE_UPDATE;
922    }
923    p->frame = frame;
924
925    /* Does not invalidate cache? */
926
927    SendToPymol(p,"frame %d\n", frame);
928    return p->status;
929}
930
931/*
932 * ClientInfoCmd --
933 *
934 *       
935 *      clientinfo path list
936 */
937static int
938ClientInfoCmd(ClientData clientData, Tcl_Interp *interp, int argc,
939              const char *argv[])
940{
941    Tcl_DString ds;
942    int result;
943    int i, numElems;
944    const char **elems;
945    char buf[BUFSIZ];
946    static int first = 1;
947    int f;
948
949    if (argc != 2) {
950        Tcl_AppendResult(interp, "wrong # of arguments: should be \"", argv[0],
951                " list\"", (char *)NULL);
952        return TCL_ERROR;
953    }
954    /* Use the initial client key value pairs as the parts for a generating
955     * a unique file name. */
956    f = GetStatsFile(argv[1]);
957    if (f < 0) {
958        Tcl_AppendResult(interp, "can't open stats file: ",
959                         Tcl_PosixError(interp), (char *)NULL);
960        return TCL_ERROR;
961    }
962    Tcl_DStringInit(&ds);
963    if (first) {
964        Tcl_DStringAppendElement(&ds, "render_start");
965        first = 0;
966    } else {
967        Tcl_DStringAppendElement(&ds, "render_info");
968    }
969    /* renderer */
970    Tcl_DStringAppendElement(&ds, "renderer");
971    Tcl_DStringAppendElement(&ds, "nanovis");
972    /* pid */
973    Tcl_DStringAppendElement(&ds, "pid");
974    sprintf(buf, "%d", getpid());
975    Tcl_DStringAppendElement(&ds, buf);
976    /* host */
977    Tcl_DStringAppendElement(&ds, "host");
978    gethostname(buf, BUFSIZ-1);
979    buf[BUFSIZ-1] = '\0';
980    Tcl_DStringAppendElement(&ds, buf);
981    /* date */
982    Tcl_DStringAppendElement(&ds, "date");
983    strcpy(buf, ctime(&stats.start.tv_sec));
984    buf[strlen(buf) - 1] = '\0';
985    Tcl_DStringAppendElement(&ds, buf);
986    /* date_secs */
987    Tcl_DStringAppendElement(&ds, "date_secs");
988    sprintf(buf, "%ld", stats.start.tv_sec);
989    Tcl_DStringAppendElement(&ds, buf);
990
991    /* Client arguments. */
992    if (Tcl_SplitList(interp, argv[2], &numElems, &elems) != TCL_OK) {
993        return TCL_ERROR;
994    }
995    for (i = 0; i < numElems; i++) {
996        Tcl_DStringAppendElement(&ds, elems[i]);
997    }
998    free(elems);
999    Tcl_DStringAppend(&ds, "\n", 1);
1000    result = WriteToStatsFile(f, Tcl_DStringValue(&ds), Tcl_DStringLength(&ds));
1001    Tcl_DStringFree(&ds);
1002    return result;
1003}
1004
1005static int
1006LabelCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1007         const char *argv[])
1008{
1009    PymolProxy *p = clientData;
1010    int i, push, defer, bool, size;
1011    const char *model;
1012
1013    clear_error(p);
1014    model = "all";
1015    size = 14;
1016    bool = TRUE;
1017    push = defer = FALSE;
1018    for(i = 1; i < argc; i++) {
1019        if (strcmp(argv[i],"-defer") == 0) {
1020            defer = TRUE;
1021        } else if (strcmp(argv[i],"-push") == 0) {
1022            push = TRUE;
1023        } else if (strcmp(argv[i],"-model") == 0) {
1024            if (++i < argc) {
1025                model = argv[i];
1026            }
1027        } else if (strcmp(argv[i],"-size") == 0) {
1028            if (++i < argc) {
1029                size = atoi(argv[i]);
1030            }
1031        } else if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
1032            return TCL_ERROR;
1033        }
1034    }
1035    p->flags |= INVALIDATE_CACHE;       /* label */
1036    if (!defer || push) {
1037        p->flags |= UPDATE_PENDING;
1038    }
1039    if (push) {
1040        p->flags |= FORCE_UPDATE;
1041    }
1042    SendToPymol(p, "set label_color,white,%s\nset label_size,%d,%s\n",
1043            model, size, model);
1044    if (bool) {
1045        SendToPymol(p, "label %s,\"%%s%%s\" %% (ID,name)\n", model);
1046    } else {
1047        SendToPymol(p, "label %s\n", model);
1048    }
1049    return p->status;
1050}
1051
1052/*
1053 * LoadPDBCmd --
1054 *
1055 *      Load a PDB into pymol.  We write the pdb data into a temporary file
1056 *      and then let pymol read it and delete it.  There is no good way to
1057 *      load PDB data into pymol without using a file.  The specially created
1058 *      routine "loadandremovepdbfile" in pymol will remove the file after
1059 *      loading it. 
1060 *
1061 */
1062static int
1063LoadPDBCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1064           const char *argv[])
1065{
1066    PymolProxy *p = clientData;
1067    const char *string;
1068    const char *name;
1069    unsigned char *allocated;
1070    int state, defer, push;
1071    size_t numBytes;
1072    int i, j;
1073    int status;
1074
1075    if (p == NULL){
1076        return TCL_ERROR;
1077    }
1078    clear_error(p);
1079    defer = push = FALSE;
1080    for(i = j = 1; i < argc; i++) {
1081        if (strcmp(argv[i],"-defer") == 0) {
1082            defer = TRUE;
1083        } else if (strcmp(argv[i],"-push") == 0) {
1084            push = TRUE;
1085        } else {
1086            if (j < i) {
1087                argv[j] = argv[i];
1088            }
1089            j++;
1090        }
1091    }
1092    argc = j;
1093    if (argc < 4) {
1094        Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1095                         " <data>|follows <model> <state> ?<numBytes>?\"",
1096                         (char *)NULL);
1097        return TCL_ERROR;
1098    }
1099    string = argv[1];
1100    name   = argv[2];
1101    if (Tcl_GetInt(interp, argv[3], &state) != TCL_OK) {
1102        return TCL_ERROR;
1103    }
1104    numBytes = 0;
1105    status = BUFFER_ERROR;
1106    if (strcmp(string, "follows") == 0) {
1107        int n;
1108
1109        if (argc != 5) {
1110            Tcl_AppendResult(interp, "wrong # arguments: should be \"", argv[0],
1111                         " follows <model> <state> <numBytes>\"", (char *)NULL);
1112            return TCL_ERROR;
1113        }
1114        if (Tcl_GetInt(interp, argv[4], &n) != TCL_OK) {
1115            return TCL_ERROR;
1116        }
1117        if (n < 0) {
1118            Tcl_AppendResult(interp, "bad value for # bytes \"", argv[4],
1119                         "\"", (char *)NULL);
1120            return TCL_ERROR;
1121        }
1122        numBytes = (size_t)n;
1123    }
1124    if (!defer || push) {
1125        p->flags |= UPDATE_PENDING;
1126    }
1127    if (push) {
1128        p->flags |= FORCE_UPDATE;
1129    }
1130    p->cacheId = state;
1131
1132    /* Does not invalidate cache? */
1133
1134    allocated = NULL;
1135    allocated = malloc(sizeof(char) * numBytes);
1136    if (allocated == NULL) {
1137        Tcl_AppendResult(interp, "can't allocate buffer for pdbdata.",
1138                         (char *)NULL);
1139        return TCL_ERROR;
1140    }
1141    status = ReadFollowingData(&p->client, allocated, numBytes);
1142    if (status != BUFFER_OK) {
1143        Tcl_AppendResult(interp, "can't read pdbdata from client.",
1144                         (char *)NULL);
1145        free(allocated);
1146        return TCL_ERROR;
1147    }
1148    string = (const char *)allocated;
1149    {
1150        int f;
1151        ssize_t numWritten;
1152        char fileName[200];
1153
1154        strcpy(fileName, "/tmp/pdb.XXXXXX");
1155        p->status = TCL_ERROR;
1156        f = mkstemp(fileName);
1157        if (f < 0) {
1158            Tcl_AppendResult(interp, "can't create temporary file \"",
1159                fileName, "\":", Tcl_PosixError(interp), (char *)NULL);
1160            goto error;
1161        }
1162        numWritten = write(f, string, numBytes);
1163        if (numBytes != numWritten) {
1164            Tcl_AppendResult(interp, "can't write PDB data to \"",
1165                fileName, "\": ", Tcl_PosixError(interp), (char *)NULL);
1166            close(f);
1167            goto error;
1168        }
1169        close(f);
1170        SendToPymol(p, "loadandremovepdbfile %s,%s,%d\n", fileName, name,
1171                    state);
1172        p->status = TCL_OK;
1173    }
1174 error:
1175    if (allocated != NULL) {
1176        free(allocated);
1177    }
1178    return p->status;
1179}
1180
1181static int
1182OrthoscopicCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1183              const char *argv[])
1184{
1185    PymolProxy *p = clientData;
1186    int bool, defer, push, i;
1187
1188    clear_error(p);
1189    defer = push = FALSE;
1190    bool = FALSE;
1191    for(i = 1; i < argc; i++) {
1192        if (strcmp(argv[i],"-defer") == 0) {
1193            defer = TRUE;
1194        } else if (strcmp(argv[i],"-push") == 0) {
1195            push = TRUE;
1196        } else {
1197            if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
1198                return TCL_ERROR;
1199            }
1200        }
1201    }
1202    p->flags |= INVALIDATE_CACHE; /* orthoscopic */
1203    if (!defer || push) {
1204        p->flags |= UPDATE_PENDING;
1205    }
1206    if (push) {
1207        p->flags |= FORCE_UPDATE;
1208    }
1209    SendToPymol(p, "set orthoscopic=%d\n", bool);
1210    return p->status;
1211}
1212
1213/*
1214 * PanCmd --
1215 *
1216 *      Issue "move" commands for changes in the x and y coordinates of the
1217 *      camera.  The problem here is that there is no event compression.
1218 *      Consecutive "pan" commands are not compressed into a single
1219 *      directive.  The means that the pymol server will render scenes that
1220 *      are not used by the client.
1221 *
1222 *      Need to 1) defer the "move" commands until we find the next command
1223 *      isn't a "pan". 2) Track the x and y coordinates as they are
1224 *      compressed.
1225 */
1226static int
1227PanCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1228{
1229    PymolProxy *p = clientData;
1230    double x, y;
1231    int i;
1232    int defer, push;
1233
1234    clear_error(p);
1235    defer = push = FALSE;
1236    for (i = 1; i < argc; i++) {
1237        if (strcmp(argv[i],"-defer") == 0) {
1238            defer = 1;
1239        } else if (strcmp(argv[i],"-push") == 0) {
1240            push = 1;
1241        } else {
1242            break;
1243        }
1244    }
1245    if ((Tcl_GetDouble(interp, argv[i], &x) != TCL_OK) ||
1246        (Tcl_GetDouble(interp, argv[i+1], &y) != TCL_OK)) {
1247        return TCL_ERROR;
1248    }
1249    p->flags |= INVALIDATE_CACHE;       /* pan */
1250    if (!defer || push) {
1251        p->flags |= UPDATE_PENDING;
1252    }
1253    if (push) {
1254        p->flags |= FORCE_UPDATE;
1255    }
1256    if ((x != 0.0f) || (y != 0.0f)) {
1257        p->xPan = x * 0.05;
1258        p->yPan = -y * 0.05;
1259        p->flags |= PAN_PENDING;
1260    }
1261    return p->status;
1262}
1263
1264static int
1265PngCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1266{
1267    PymolProxy *p = clientData;
1268
1269    clear_error(p);
1270
1271    /* Force pymol to update the current scene. */
1272    SendToPymol(p, "refresh\n");
1273    /* This is a hack. We're encoding the filename to pass extra information
1274     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1275     * parameters of a new "molvispng" command that would be passed all the
1276     * way through to MyPNGWrite.
1277     *
1278     * The extra information is contained in the token we get from the
1279     * molvisviewer client, the frame number, and rock offset. */
1280    SendToPymol(p, "png -:%d:%d:%d\n", p->cacheId, p->frame, p->rockOffset);
1281    return p->status;
1282}
1283
1284
1285static int
1286PpmCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1287{
1288    PymolProxy *p = clientData;
1289
1290    clear_error(p);
1291
1292    /* Force pymol to update the current scene. */
1293    SendToPymol(p, "refresh\n");
1294    /* This is a hack. We're encoding the filename to pass extra information
1295     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1296     * parameters of a new "molvispng" command that would be passed all the
1297     * way through to MyPNGWrite.
1298     *
1299     * The extra information is contained in the token we get from the
1300     * molvisviewer client, the frame number, and rock offset. */
1301    SendToPymol(p, "png -:%d:%d:%d,format=1\n", p->cacheId, p->frame,
1302            p->rockOffset);
1303    p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
1304    return p->status;
1305}
1306
1307
1308static int
1309PrintCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1310         const char *argv[])
1311{
1312    PymolProxy *p = clientData;
1313    int width, height;
1314    const char *token, *bgcolor;
1315
1316    clear_error(p);
1317
1318    if (argc != 5) {
1319        Tcl_AppendResult(interp, "wrong # arguments: should be \"",
1320                         argv[0], " token width height color\"", (char *)NULL);
1321        return TCL_ERROR;
1322    }
1323    token = argv[1];
1324    if (Tcl_GetInt(interp, argv[2], &width) != TCL_OK) {
1325        return TCL_ERROR;
1326    }
1327    if (Tcl_GetInt(interp, argv[3], &height) != TCL_OK) {
1328        return TCL_ERROR;
1329    }
1330    bgcolor = argv[4];
1331    /* Force pymol to update the current scene. */
1332    if (strcmp(bgcolor, "none") == 0) {
1333        SendToPymol(p, "set ray_opaque_background,off\n");
1334        SendToPymol(p, "refresh\n", bgcolor);
1335    } else {
1336        SendToPymol(p, "set ray_opaque_background,on\n");
1337        SendToPymol(p, "bg_color %s\nrefresh\n", bgcolor);
1338    }
1339    /* This is a hack. We're encoding the filename to pass extra information
1340     * to the MyPNGWrite routine inside of pymol. Ideally these would be
1341     * parameters of a new "molvispng" command that would be passed all the
1342     * way through to MyPNGWrite. 
1343     *
1344     * The extra information is contained in the token we get from the
1345     * molvisviewer client, the frame number, and rock offset.
1346     */
1347    SendToPymol(p, "png -:%s:0:0,width=%d,height=%d,ray=1,dpi=300\n",
1348            token, width, height);
1349    SendToPymol(p, "bg_color black\n");
1350    return p->status;
1351}
1352
1353static int
1354RawCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1355{
1356    PymolProxy *p = clientData;
1357    int arg, defer = 0, push = 0;
1358    const char *cmd;
1359    clear_error(p);
1360
1361    cmd = NULL;
1362    defer = push = FALSE;
1363    for(arg = 1; arg < argc; arg++) {
1364        if (strcmp(argv[arg], "-defer") == 0)
1365            defer = 1;
1366        else if (strcmp(argv[arg], "-push") == 0)
1367            push = 1;
1368        else {
1369            cmd = argv[arg];
1370        }
1371    }
1372
1373    p->flags |= INVALIDATE_CACHE; /* raw */
1374    if (!defer || push) {
1375        p->flags |= UPDATE_PENDING;
1376    }
1377    if (push) {
1378        p->flags |= FORCE_UPDATE;
1379    }
1380    SendToPymol(p,"%s\n", cmd);
1381    return p->status;
1382}
1383
1384static int
1385ResetCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1386         const char *argv[])
1387{
1388    PymolProxy *p = clientData;
1389    int arg, push = 0, defer = 0;
1390
1391    clear_error(p);
1392    defer = push = 0;
1393    for(arg = 1; arg < argc; arg++) {
1394        if ( strcmp(argv[arg],"-defer") == 0 )
1395            defer = 1;
1396        else if (strcmp(argv[arg],"-push") == 0 )
1397            push = 1;
1398    }
1399               
1400    p->flags |= INVALIDATE_CACHE; /* reset */
1401    if (!defer || push) {
1402        p->flags |= UPDATE_PENDING;
1403    }
1404    if (push) {
1405        p->flags |= FORCE_UPDATE;
1406    }
1407    SendToPymol(p, "reset\nzoom complete=1\n");
1408    return p->status;
1409}
1410
1411static int
1412RockCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1413        const char *argv[])
1414{
1415    PymolProxy *p = clientData;
1416    float y = 0.0;
1417    int arg, push, defer;
1418
1419    clear_error(p);
1420
1421    defer = push = FALSE;
1422    for(arg = 1; arg < argc; arg++) {
1423        if ( strcmp(argv[arg],"-defer") == 0 )
1424            defer = 1;
1425        else if (strcmp(argv[arg],"-push") == 0 )
1426            push = 1;
1427        else
1428            y = atof( argv[arg] );
1429    }
1430               
1431    /* Does not invalidate cache. */
1432
1433    if (!defer || push) {
1434        p->flags |= UPDATE_PENDING;
1435    }
1436    if (push) {
1437        p->flags |= FORCE_UPDATE;
1438    }
1439    SendToPymol(p,"turn y, %f\n", y - p->rockOffset);
1440    p->rockOffset = y;
1441    return p->status;
1442}
1443
1444static int
1445RepresentationCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1446                  const char *argv[])
1447{
1448    PymolProxy *p = clientData;
1449    const char *model;
1450    const char *rep;
1451    int defer, push, i;
1452
1453    clear_error(p);
1454    defer = push = FALSE;
1455    model = "all";
1456    rep = NULL;
1457    for (i = 1; i < argc; i++) {
1458        if (strcmp(argv[i],"-defer") == 0 ) {
1459            defer = TRUE;
1460        } else if (strcmp(argv[i],"-push") == 0) {
1461            push = TRUE;
1462        } else if (strcmp(argv[i],"-model") == 0) {
1463            if (++i < argc) {
1464                model = argv[i];
1465            }
1466        } else {
1467            rep = argv[i];
1468        }
1469    }
1470    if (rep == NULL) {
1471        Tcl_AppendResult(interp, "missing representation argument",
1472                         (char *)NULL);
1473        return TCL_ERROR;
1474    }
1475
1476    p->flags |= INVALIDATE_CACHE; /* representation */
1477    if (!defer || push) {
1478        p->flags |= UPDATE_PENDING;
1479    }
1480    if (push) {
1481        p->flags |= FORCE_UPDATE;
1482    }
1483    if (strcmp(rep, "ballnstick") == 0) { /* Ball 'n Stick */
1484        SendToPymol(p,
1485              "set stick_color,white,%s\n"
1486              "show sticks,%s\n"
1487              "show spheres,%s\n"
1488              "hide lines,%s\n"
1489              "hide cartoon,%s\n",
1490              model, model, model, model, model);
1491    } else if (strcmp(rep, "spheres") == 0) { /* spheres */   
1492        SendToPymol(p,
1493              "hide sticks,%s\n"
1494              "show spheres,%s\n"
1495              "hide lines,%s\n"
1496              "hide cartoon,%s\n"
1497              "set sphere_quality,2,%s\n"
1498              "set ambient,.2,%s\n",
1499              model, model, model, model, model, model);
1500    } else if (strcmp(rep, "none") == 0) { /* nothing */   
1501        SendToPymol(p,
1502              "hide sticks,%s\n",
1503              "hide spheres,%s\n"
1504              "hide lines,%s\n"
1505              "hide cartoon,%s\n",
1506              model, model, model, model);
1507    } else if (strcmp(rep, "sticks") == 0) { /* sticks */   
1508        SendToPymol(p,
1509              "set stick_color,white,%s\n"
1510              "show sticks,%s\n"
1511              "hide spheres,%s\n"
1512              "hide lines,%s\n"
1513              "hide cartoon,%s\n",
1514              model, model, model, model, model);
1515    } else if (strcmp(rep, "lines") == 0) { /* lines */   
1516        SendToPymol(p,
1517              "hide sticks,%s\n"
1518              "hide spheres,%s\n"
1519              "show lines,%s\n"
1520              "hide cartoon,%s\n",
1521              model, model, model, model);
1522    } else if (strcmp(rep, "cartoon") == 0) { /* cartoon */   
1523        SendToPymol(p,
1524              "hide sticks,%s\n"
1525              "hide spheres,%s\n"
1526              "hide lines,%s\n"
1527              "show cartoon,%s\n",
1528              model, model, model, model);
1529    }
1530    return p->status;
1531}
1532
1533/*
1534 * RotateCmd --
1535 *
1536 *      Issue "turn" commands for changes in the angle of the camera.  The
1537 *      problem here is that there is no event compression.  Consecutive
1538 *      "rotate" commands are not compressed into a single directive.  The
1539 *      means that the pymol server will render many scene that are not used
1540 *      by the client.
1541 *
1542 *      Need to 1) defer the "turn" commands until we find the next command
1543 *      isn't a "rotate". 2) Track the rotation angles as they are compressed.
1544 */
1545static int
1546RotateCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1547          const char *argv[])
1548{
1549    PymolProxy *p = clientData;
1550    float xAngle, yAngle, zAngle;
1551    int defer, push, arg, varg = 1;
1552
1553    clear_error(p);
1554    defer = push = 0;
1555    xAngle = yAngle = zAngle = 0.0f;
1556    for(arg = 1; arg < argc; arg++) {
1557        if (strcmp(argv[arg],"-defer") == 0) {
1558            defer = 1;
1559        } else if (strcmp(argv[arg],"-push") == 0) {
1560            push = 1;
1561        } else  if (varg == 1) {
1562            double value;
1563            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1564                return TCL_ERROR;
1565            }
1566            xAngle = (float)value;
1567            varg++;
1568        } else if (varg == 2) {
1569            double value;
1570            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1571                return TCL_ERROR;
1572            }
1573            yAngle = (float)value;
1574            varg++;
1575        } else if (varg == 3) {
1576            double value;
1577            if (Tcl_GetDouble(interp, argv[arg], &value) != TCL_OK) {
1578                return TCL_ERROR;
1579            }
1580            zAngle = (float)value;
1581            varg++;
1582        }
1583    }
1584    p->flags |= INVALIDATE_CACHE; /* rotate */
1585    if (!defer || push) {
1586        p->flags |= UPDATE_PENDING;
1587    }
1588    if (push) {
1589        p->flags |= FORCE_UPDATE;
1590    }
1591    if ((xAngle != 0.0f) || (yAngle != 0.0f) || (zAngle != 0.0f)) {
1592        p->xAngle += xAngle;
1593        p->yAngle += yAngle;
1594        p->zAngle += zAngle;
1595        p->flags |= ROTATE_PENDING;
1596    }
1597    return p->status;
1598}
1599
1600static int
1601ScreenCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1602          const char *argv[])
1603{
1604    PymolProxy *p = clientData;
1605    int width = -1, height = -1;
1606    int defer, push, i, varg;
1607
1608    clear_error(p);
1609    defer = push = FALSE;
1610    varg = 1;
1611    for(i = 1; i < argc; i++) {
1612        if ( strcmp(argv[i],"-defer") == 0 )
1613            defer = 1;
1614        else if ( strcmp(argv[i], "-push") == 0 )
1615            push = 1;
1616        else if (varg == 1) {
1617            width = atoi(argv[i]);
1618            height = width;
1619            varg++;
1620        }
1621        else if (varg == 2) {
1622            height = atoi(argv[i]);
1623            varg++;
1624        }
1625    }
1626    if ((width < 0) || (height < 0)) {
1627        return TCL_ERROR;
1628    }
1629    p->flags |= INVALIDATE_CACHE; /* viewport */
1630    if (!defer || push) {
1631        p->flags |= UPDATE_PENDING;
1632    }
1633    if (push) {
1634        p->flags |= FORCE_UPDATE;
1635    }
1636    p->width = width;
1637    p->height = height;
1638    p->flags |= VIEWPORT_PENDING;
1639    return p->status;
1640}
1641
1642static int
1643SphereScaleCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1644           const char *argv[])
1645{
1646    int defer = 0, push = 0, i;
1647    double scale;
1648    const char *model = "all";
1649    PymolProxy *p = clientData;
1650
1651    clear_error(p);
1652    scale = 0.25f;
1653    for(i = 1; i < argc; i++) {
1654        if ( strcmp(argv[i],"-defer") == 0 ) {
1655            defer = 1;
1656        } else if (strcmp(argv[i],"-push") == 0) {
1657            push = 1;
1658        } else if (strcmp(argv[i],"-model") == 0) {
1659            if (++i < argc) {
1660                model = argv[i];
1661            }
1662        } else {
1663            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1664                return TCL_ERROR;
1665            }
1666        }
1667    }
1668    p->flags |= INVALIDATE_CACHE;  /* sphere_scale */
1669    if (!defer || push) {
1670        p->flags |= UPDATE_PENDING;
1671    }
1672    if (push) {
1673        p->flags |= FORCE_UPDATE;
1674    }
1675    if (strcmp(model, "all") == 0) {
1676        p->flags |= ATOM_SCALE_PENDING;
1677        p->sphereScale = scale;
1678    } else {
1679        SendToPymol(p, "set sphere_scale,%f,%s\n", scale, model);
1680    }
1681    return p->status;
1682}
1683
1684static int
1685StickRadiusCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1686               const char *argv[])
1687{
1688    PymolProxy *p = clientData;
1689    int defer = 0, push = 0, i;
1690    double scale;
1691    const char *model = "all";
1692
1693    clear_error(p);
1694    scale = 0.25f;
1695    for(i = 1; i < argc; i++) {
1696        if (strcmp(argv[i],"-defer") == 0 ) {
1697            defer = 1;
1698        } else if (strcmp(argv[i],"-push") == 0) {
1699            push = 1;
1700        } else if (strcmp(argv[i],"-model") == 0) {
1701            if (++i < argc)
1702                model = argv[i];
1703        } else {
1704            if (Tcl_GetDouble(interp, argv[i], &scale) != TCL_OK) {
1705                return TCL_ERROR;
1706            }
1707        }
1708    }
1709    p->flags |= INVALIDATE_CACHE;  /* stick_radius */
1710    if (!defer || push) {
1711        p->flags |= UPDATE_PENDING;
1712    }
1713    if (push) {
1714        p->flags |= FORCE_UPDATE;
1715    }
1716
1717    if (strcmp(model, "all") == 0) {
1718        p->flags |= STICK_RADIUS_PENDING;
1719        p->stickRadius = scale;
1720    } else {
1721        SendToPymol(p, "set stick_radius,%f,%s\n", scale, model);
1722    }
1723    return p->status;
1724}
1725
1726static int
1727TransparencyCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1728                const char *argv[])
1729{
1730    PymolProxy *p = clientData;
1731    const char *model;
1732    float transparency;
1733    int defer, push;
1734    int i;
1735
1736    clear_error(p);
1737    model = "all";
1738    defer = push = FALSE;
1739    transparency = 0.0f;
1740    for(i = 1; i < argc; i++) {
1741        if ( strcmp(argv[i],"-defer") == 0 ) {
1742            defer = 1;
1743        } else if (strcmp(argv[i],"-push") == 0) {
1744            push = 1;
1745        } else if (strcmp(argv[i],"-model") == 0) {
1746            if (++i < argc) {
1747                model = argv[i];
1748            }
1749        } else {
1750            transparency = atof(argv[i]);
1751        }
1752    }
1753    p->flags |= INVALIDATE_CACHE; /* transparency */
1754    if (!defer || push) {
1755        p->flags |= UPDATE_PENDING;
1756    }
1757    if (push) {
1758        p->flags |= FORCE_UPDATE;
1759    }
1760    SendToPymol(p,
1761          "set sphere_transparency,%g,%s\n"
1762          "set stick_transparency,%g,%s\n"
1763          "set cartoon_transparency,%g,%s\n",
1764          transparency, model, transparency, model,
1765          transparency, model);
1766    return p->status;
1767}
1768
1769static int
1770VMouseCmd(ClientData clientData, Tcl_Interp *interp, int argc,
1771          const char *argv[])
1772{
1773    PymolProxy *p = clientData;
1774    int i, defer = 0, push = 0, varg = 1;
1775    int arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0, arg5 = 0;
1776
1777    clear_error(p);
1778
1779    for(i = 1; i < argc; i++) {
1780        if (strcmp(argv[i], "-defer") == 0)
1781            defer = 1;
1782        else if (strcmp(argv[i], "-push") == 0)
1783            push = 1;
1784        else if (varg == 1) {
1785            arg1 = atoi(argv[i]);
1786            varg++;
1787        } else if (varg == 2) {
1788            arg2 = atoi(argv[i]);
1789            varg++;
1790        } else if (varg == 3) {
1791            arg3 = atoi(argv[i]);
1792            varg++;
1793        } else if (varg == 4) {
1794            arg4 = atoi(argv[i]);
1795            varg++;
1796        } else if (varg == 5) {
1797            arg5 = atoi(argv[i]);
1798            varg++;
1799        }
1800    }
1801
1802    p->flags |= INVALIDATE_CACHE;       /* vmouse */
1803    if (!defer || push) {
1804        p->flags |= UPDATE_PENDING;
1805    }
1806    if (push) {
1807        p->flags |= FORCE_UPDATE;
1808    }
1809    SendToPymol(p, "vmouse %d,%d,%d,%d,%d\n", arg1, arg2, arg3, arg4, arg5);
1810    return p->status;
1811}
1812
1813
1814/*
1815 * ZoomCmd --
1816 *
1817 *      Issue "move" commands for changes in the z-coordinate of the camera.
1818 *      The problem here is that there is no event compression.  Consecutive
1819 *      "zoom" commands are not compressed into a single directive.  The means
1820 *      that the pymol server will render scenes that are not used by the
1821 *      client.
1822 *
1823 *      Need to 1) defer the "move" commands until we find the next command
1824 *      isn't a "zoom". 2) Track the z-coordinate as they are compressed.
1825 */
1826static int
1827ZoomCmd(ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[])
1828{
1829    PymolProxy *p = clientData;
1830    double factor = 0.0;
1831    int defer = 0, push = 0, i, varg = 1;
1832
1833    clear_error(p);
1834
1835    for(i = 1; i < argc; i++) {
1836        if (strcmp(argv[i],"-defer") == 0)
1837            defer = 1;
1838        else if (strcmp(argv[i],"-push") == 0)
1839            push = 1;
1840        else if (varg == 1) {
1841            double value;
1842            if (Tcl_GetDouble(interp, argv[i], &value) != TCL_OK) {
1843                return TCL_ERROR;
1844            }
1845            factor = (float)value;
1846            varg++;
1847        }
1848    }
1849    p->flags |= INVALIDATE_CACHE; /* Zoom */
1850    if (!defer || push) {
1851        p->flags |= UPDATE_PENDING;
1852    }
1853    if (push) {
1854        p->flags |= FORCE_UPDATE;
1855    }
1856    if (factor != 0.0) {
1857        p->zoom = factor;
1858        p->flags |= ZOOM_PENDING;
1859    }
1860    return p->status;
1861}
1862
1863       
1864
1865static int
1866ExecuteCommand(Tcl_Interp *interp, Tcl_DString *dsPtr)
1867{
1868    struct timeval tv;
1869    double start, finish;
1870    int result;
1871
1872    gettimeofday(&tv, NULL);
1873    start = CVT2SECS(tv);
1874
1875#if EXEC_DEBUG
1876    DEBUG("command from client is (%s)", Tcl_DStringValue(dsPtr));
1877#endif
1878    result = Tcl_Eval(interp, Tcl_DStringValue(dsPtr));
1879    if (result != TCL_OK) {
1880#if EXEC_DEBUG
1881        DEBUG("result was %s\n", Tcl_GetString(Tcl_GetObjResult(interp)));
1882#endif
1883    }
1884    gettimeofday(&tv, NULL);
1885    finish = CVT2SECS(tv);
1886
1887    stats.cmdTime += finish - start;
1888    stats.numCommands++;
1889    Tcl_DStringSetLength(dsPtr, 0);
1890    return result;
1891}
1892
1893static void
1894SetViewport(PymolProxy *p)
1895{
1896    if (p->flags & VIEWPORT_PENDING) {
1897        SendToPymol(p, "viewport %d,%d\n", p->width, p->height);
1898        SendToPymol(p, "refresh\n");
1899        p->flags &= ~VIEWPORT_PENDING;
1900    }
1901}
1902
1903static void
1904SetZoom(PymolProxy *p)
1905{
1906    if (p->flags & ZOOM_PENDING) {
1907        SendToPymol(p, "move z,%f\n", p->zoom);
1908        p->flags &= ~ZOOM_PENDING;
1909    }
1910}
1911
1912static void
1913SetPan(PymolProxy *p)
1914{
1915    if (p->flags & PAN_PENDING) {
1916        SendToPymol(p, "move x,%f\nmove y,%f\n", p->xPan, p->yPan);
1917        p->flags &= ~PAN_PENDING;
1918    }
1919}
1920
1921static void
1922SetRotation(PymolProxy *p)
1923{
1924    if (p->flags & ROTATE_PENDING) {
1925        /* Every pymol command line generates a new rendering. Execute all
1926         * three turns as a single command line. */
1927        SendToPymol(p,"turn x,%f\nturn y,%f\nturn z,%f\n", p->xAngle, p->yAngle,
1928                p->zAngle);
1929        p->xAngle = p->yAngle = p->zAngle = 0.0f;
1930        p->flags &= ~ROTATE_PENDING;
1931    }
1932}
1933
1934static void
1935SetSphereScale(PymolProxy *p)
1936{
1937    if (p->flags & ATOM_SCALE_PENDING) {
1938        SendToPymol(p, "set sphere_scale,%f,all\n", p->sphereScale);
1939        p->flags &= ~ATOM_SCALE_PENDING;
1940    }
1941}
1942
1943static void
1944SetStickRadius(PymolProxy *p)
1945{
1946    if (p->flags & STICK_RADIUS_PENDING) {
1947        SendToPymol(p, "set stick_radius,%f,all\n", p->stickRadius);
1948        p->flags &= ~STICK_RADIUS_PENDING;
1949    }
1950}
1951
1952static void
1953UpdateSettings(PymolProxy *p)
1954{
1955    /* Handle all the pending setting changes now. */
1956    if (p->flags & VIEWPORT_PENDING) {
1957        SetViewport(p);
1958    }
1959    if (p->flags & ROTATE_PENDING) {
1960        SetRotation(p);
1961    }
1962    if (p->flags & PAN_PENDING) {
1963        SetPan(p);
1964    }
1965    if (p->flags & ZOOM_PENDING) {
1966        SetZoom(p);
1967    }
1968    if (p->flags & ATOM_SCALE_PENDING) {
1969        SetSphereScale(p);
1970    }
1971    if (p->flags & STICK_RADIUS_PENDING) {
1972        SetStickRadius(p);
1973    }
1974}
1975
1976static Image *
1977NewImage(ImageList *listPtr, size_t dataLength)
1978{
1979    Image *imgPtr;
1980    static int id = 0;
1981
1982    imgPtr = malloc(sizeof(Image) + dataLength);
1983    if (imgPtr == NULL) {
1984        ERROR("can't allocate image of %lu bytes",
1985              (unsigned long)(sizeof(Image) + dataLength));
1986        abort();
1987    }
1988    imgPtr->prevPtr = imgPtr->nextPtr = NULL;
1989    imgPtr->bytesLeft = dataLength;
1990    imgPtr->id = id++;
1991#if WRITE_DEBUG
1992    DEBUG("NewImage: allocating image %d of %d bytes", imgPtr->id, dataLength);
1993#endif
1994    if (listPtr->headPtr != NULL) {
1995        listPtr->headPtr->prevPtr = imgPtr;
1996    }
1997    imgPtr->nextPtr = listPtr->headPtr;
1998    if (listPtr->tailPtr == NULL) {
1999        listPtr->tailPtr = imgPtr;
2000    }
2001    listPtr->headPtr = imgPtr;
2002    imgPtr->numWritten = 0;
2003    return imgPtr;
2004}
2005
2006INLINE static void
2007FreeImage(Image *imgPtr)
2008{
2009    assert(imgPtr != NULL);
2010    free(imgPtr);
2011}
2012
2013
2014static void
2015WriteImages(ImageList *listPtr, int fd)
2016{
2017    Image *imgPtr, *prevPtr;
2018
2019    if (listPtr->tailPtr == NULL) {
2020        ERROR("Should not be here: no image available to write");
2021        return;
2022    }
2023#if WRITE_DEBUG
2024        DEBUG("Entering WriteImages");
2025#endif
2026    for (imgPtr = listPtr->tailPtr; imgPtr != NULL; imgPtr = prevPtr) {
2027        ssize_t bytesLeft;
2028
2029        assert(imgPtr->nextPtr == NULL);
2030        prevPtr = imgPtr->prevPtr;
2031#if WRITE_DEBUG
2032        DEBUG("WriteImages: image %d of %d bytes.", imgPtr->id,
2033              imgPtr->bytesLeft);
2034#endif
2035        for (bytesLeft = imgPtr->bytesLeft; bytesLeft > 0; /*empty*/) {
2036            ssize_t numWritten;
2037#if WRITE_DEBUG
2038            DEBUG("image %d: bytesLeft=%d", imgPtr->id, bytesLeft);
2039#endif
2040            numWritten = write(fd, imgPtr->data + imgPtr->numWritten, bytesLeft);
2041#if WRITE_DEBUG
2042            DEBUG("image %d: wrote %d bytes.", imgPtr->id, numWritten);
2043#endif
2044            if (numWritten < 0) {
2045                ERROR("Error writing fd=%d, %s", fd, strerror(errno));
2046#if WRITE_DEBUG
2047                DEBUG("Abnormal exit WriteImages");
2048#endif
2049                return;
2050            }
2051            bytesLeft -= numWritten;
2052            if (bytesLeft > 0) {
2053#if WRITE_DEBUG
2054                DEBUG("image %d, wrote a short buffer, %d bytes left.",
2055                      imgPtr->id, bytesLeft);
2056#endif
2057                /* Wrote a short buffer, means we would block. */
2058                imgPtr->numWritten += numWritten;
2059                imgPtr->bytesLeft = bytesLeft;
2060#if WRITE_DEBUG
2061                DEBUG("Abnormal exit WriteImages");
2062#endif
2063                return;
2064            }
2065            imgPtr->numWritten += numWritten;
2066        }
2067        /* Check if image is on the head.  */
2068        listPtr->tailPtr = prevPtr;
2069        if (prevPtr != NULL) {
2070            prevPtr->nextPtr = NULL;
2071        }
2072        FreeImage(imgPtr);
2073    }
2074    listPtr->headPtr = NULL;
2075#if WRITE_DEBUG
2076    DEBUG("Exit WriteImages");
2077#endif
2078}
2079
2080
2081static void
2082ChildHandler(int sigNum)
2083{
2084    ERROR("pymol (%d) died unexpectedly", stats.pid);
2085    pymolIsAlive = FALSE;
2086    /*DoExit(1);*/
2087}
2088
2089typedef struct {
2090    const char *name;
2091    Tcl_CmdProc *proc;
2092} CmdProc;
2093
2094static CmdProc cmdProcs[] = {
2095    { "cartoon",        CartoonCmd        },       
2096    { "cartoontrace",   CartoonTraceCmd   }, 
2097    { "clientinfo",     ClientInfoCmd     },
2098    { "disable",        DisableCmd        },       
2099    { "enable",         EnableCmd         },       
2100    { "frame",          FrameCmd          },         
2101    { "label",          LabelCmd          },         
2102    { "loadpdb",        LoadPDBCmd        },       
2103    { "orthoscopic",    OrthoscopicCmd    },   
2104    { "pan",            PanCmd            },           
2105    { "png",            PngCmd            },           
2106    { "ppm",            PpmCmd            },           
2107    { "print",          PrintCmd          },         
2108    { "raw",            RawCmd            },           
2109    { "representation", RepresentationCmd },
2110    { "reset",          ResetCmd          },         
2111    { "rock",           RockCmd           },         
2112    { "rotate",         RotateCmd         },       
2113    { "screen",         ScreenCmd         },       
2114    { "spherescale",    SphereScaleCmd    },   
2115    { "stickradius",    StickRadiusCmd    },   
2116    { "transparency",   TransparencyCmd   }, 
2117    { "viewport",       ScreenCmd         },       
2118    { "vmouse",         VMouseCmd         },       
2119    { "zoom",           ZoomCmd           },         
2120    { NULL,             NULL              }
2121};
2122
2123static int
2124InitProxy(PymolProxy *p, const char *fileName, char *const *argv)
2125{
2126    int sin[2], sout[2];                /* Pipes to connect to server. */
2127    pid_t child;
2128    struct timeval end;
2129
2130#if DEBUG
2131    DEBUG("Entering InitProxy\n");
2132#endif
2133    /* Create tow pipes for communication with the external application. One
2134     * each for the applications's: stdin and stdout.  */
2135
2136    if (pipe(sin) == -1) {
2137        return -1;
2138    }
2139    if (pipe(sout) == -1) {
2140        close(sin[0]);
2141        close(sin[1]);
2142        return -1;
2143    }
2144
2145    /* Fork the new process.  Connect I/O to the new socket.  */
2146    child = fork();
2147    if (child < 0) {
2148        ERROR("can't fork process: %s\n", strerror(errno));
2149        return -3;
2150    }
2151
2152    if (child == 0) {                   /* Child process */
2153        int f;
2154
2155        /*
2156         * Create a new process group, so we can later kill this process and
2157         * all its children without affecting the process that created this
2158         * one.
2159         */
2160        setpgid(child, 0);
2161       
2162        /* Redirect stdin, stdout, and stderr to pipes before execing */
2163
2164        dup2(sin[0],  0);               /* Server standard input */
2165        dup2(sout[1], 1);               /* Server standard output */
2166        f = open(fileName, O_WRONLY | O_CREAT | O_TRUNC, 0600);
2167        if (f < 0) {
2168            ERROR("can't open server error file `%s': %s", fileName,
2169                  strerror(errno));
2170            exit(1);
2171        }
2172        dup2(f, 2);                     /* Redirect stderr to a file */
2173       
2174        /* Close all other descriptors  */       
2175        for (f = 3; f < FD_SETSIZE; f++) {
2176            close(f);
2177        }
2178        INFO("attempting to start \"%s\"", argv[0]);
2179        execvp(argv[0], argv);
2180        ERROR("can't exec `%s': %s", argv[0], strerror(errno));
2181        exit(-1);
2182    } else {
2183        pymolIsAlive = TRUE;
2184        signal(SIGCHLD, ChildHandler);
2185    }
2186    stats.pid = child;
2187
2188#if DEBUG
2189    DEBUG("Started %s DISPLAY=%s\n", argv[0], getenv("DISPLAY"));
2190#endif
2191    /* close opposite end of pipe, these now belong to the child process  */
2192    close(sin[0]);
2193    close(sout[1]);
2194
2195    memset(p, 0, sizeof(PymolProxy));
2196    p->sin        = sin[1];
2197    p->sout       = sout[0];
2198    p->cin        = fileno(stdout);
2199    p->cout       = fileno(stdin);
2200    p->flags      = CAN_UPDATE;
2201    p->frame      = 1;
2202    p->pid      = child;
2203    InitReadBuffer(&p->client, "client", p->cout, 1<<16);
2204    InitReadBuffer(&p->server, "server", p->sout, 1<<18);
2205
2206    /* Create safe interpreter and add pymol-specific commands to it. */
2207    {
2208        Tcl_Interp *interp;
2209        CmdProc *cp;
2210
2211        interp = Tcl_CreateInterp();
2212        Tcl_MakeSafe(interp);
2213
2214        for (cp = cmdProcs; cp->name != NULL; cp++) {
2215#if DEBUG
2216            DEBUG("Adding command %s\n", cp->name);
2217#endif
2218            Tcl_CreateCommand(interp, cp->name, cp->proc, p, NULL);
2219        }
2220        p->interp = interp;
2221    }
2222    gettimeofday(&end, NULL);
2223    stats.start = end;
2224    return 1;
2225}
2226
2227static int
2228FreeProxy(PymolProxy *p)
2229{
2230    int result, status;
2231
2232#if DEBUG
2233    DEBUG("Enter FreeProxy");
2234#endif
2235    close(p->cout);
2236    close(p->sout);
2237    close(p->cin);
2238    close(p->sin);
2239
2240    Tcl_DeleteInterp(p->interp);
2241    ServerStats(0);
2242
2243#if DEBUG
2244    DEBUG("Waiting for pymol server to exit");
2245#endif
2246    alarm(5);
2247    if (waitpid(p->pid, &result, 0) < 0) {
2248        ERROR("error waiting on pymol server to exit: %s", strerror(errno));
2249    }
2250#if DEBUG
2251    DEBUG("attempting to signal (SIGTERM) pymol server.");
2252#endif
2253    kill(-p->pid, SIGTERM);             // Kill process group
2254    alarm(5);
2255   
2256#if DEBUG
2257    DEBUG("Waiting for pymol server to exit after SIGTERM");
2258#endif
2259    if (waitpid(p->pid, &result, 0) < 0) {
2260        if (errno != ECHILD) {
2261            ERROR("error waiting on pymol server to exit after SIGTERM: %s",
2262                  strerror(errno));
2263        }
2264    }
2265    status = -1;
2266    while ((status == -1) && (errno == EINTR)) {
2267#if DEBUG
2268        DEBUG("Attempting to signal (SIGKILL) pymol server.");
2269#endif
2270        kill(-p->pid, SIGKILL);         // Kill process group
2271        alarm(10);
2272#if DEBUG
2273        DEBUG("Waiting for pymol server to exit after SIGKILL");
2274#endif
2275        status = waitpid(p->pid, &result, 0);
2276        alarm(0);
2277    }
2278    INFO("pymol server process ended (result=%d)", result);
2279
2280    if (WIFEXITED(result)) {
2281        result = WEXITSTATUS(result);
2282    }
2283    unlink(stderrFile);
2284    return result;
2285}
2286
2287
2288static void *
2289ClientToServer(void *clientData)
2290{
2291    PymolProxy *p = clientData;
2292    Tcl_DString command;
2293    struct timeval tv, *tvPtr;
2294
2295#if READ_DEBUG
2296    DEBUG("Reader thread started");
2297#endif
2298    Tcl_DStringInit(&command);
2299    while (pymolIsAlive) {
2300        tvPtr = NULL;
2301#if READ_DEBUG
2302        DEBUG("Start I/O set");
2303#endif
2304        while ((pymolIsAlive) && (WaitForNextLine(&p->client, tvPtr))) {
2305            size_t numBytes;
2306            const char *line;
2307            int status;
2308
2309            status = GetLine(&p->client, &numBytes, &line);
2310            if (status != BUFFER_OK) {
2311                ERROR("can't read client stdout (numBytes=%d): %s\n", numBytes,
2312                      strerror(errno));
2313                goto done;
2314            }
2315            Tcl_DStringAppend(&command, line, numBytes);
2316            if (Tcl_CommandComplete(Tcl_DStringValue(&command))) {
2317                int result;
2318                   
2319                /* May execute more than one command. */
2320                result = ExecuteCommand(p->interp, &command);
2321                if (result == TCL_BREAK) {
2322#if READ_DEBUG
2323                    DEBUG("TCL_BREAK found");
2324#endif
2325                    break;              /* This was caused by a "imgflush"
2326                                         * command. Break out of the read loop
2327                                         * and allow a new image to be
2328                                         * rendered. */
2329                }
2330                if (p->flags & FORCE_UPDATE) {
2331#if READ_DEBUG
2332                    DEBUG("FORCE_UPDATE set");
2333#endif
2334                    break;
2335                }
2336            }
2337            tv.tv_sec = 0L;
2338            tv.tv_usec = 0L;    /* On successive reads, we break
2339                                         *  out * if no data is
2340                                         *  available. */
2341            tvPtr = &tv;                       
2342        }
2343#if READ_DEBUG
2344        DEBUG("Finish I/O set");
2345#endif
2346        /* Handle all the pending setting changes now. */
2347        UpdateSettings(p);
2348
2349        /* We might want to refresh the image if we're not currently
2350         * transmitting an image back to the client. The image will be
2351         * refreshed after the image has been completely transmitted. */
2352        if (p->flags & UPDATE_PENDING) {
2353#if READ_DEBUG
2354            DEBUG("calling ppm because of update");
2355#endif
2356            Tcl_Eval(p->interp, "ppm");
2357            p->flags &= ~(UPDATE_PENDING|FORCE_UPDATE);
2358        }
2359    }
2360 done:
2361#if READ_DEBUG
2362    DEBUG("Leaving Reader thread");
2363#endif
2364    return NULL;
2365}
2366
2367static void *
2368ServerToClient(void *clientData)
2369{
2370    ReadBuffer *bp = clientData;
2371    ImageList list;
2372
2373#if WRITE_DEBUG
2374    DEBUG("Writer thread started");
2375#endif
2376    list.headPtr = list.tailPtr = NULL;
2377    while (pymolIsAlive) {
2378        while (WaitForNextLine(bp, NULL)) {
2379            Image *imgPtr;
2380            const char *line;
2381            char header[200];
2382            size_t len;
2383            int numBytes;
2384            size_t hdrLength;
2385            int frameNum, rockOffset;
2386            char cacheId[200];
2387
2388            /* Keep reading lines untils we find a "image follows:" line */
2389            if (GetLine(bp, &len, &line) != BUFFER_OK) {
2390#if WRITE_DEBUG
2391                DEBUG("Leaving Writer thread");
2392#endif
2393                return NULL;
2394            }
2395#if WRITE_DEBUG
2396            DEBUG("Writer: line found is %s\n", line);
2397#endif
2398            if (strncmp(line, "image follows: ", 15) != 0) {
2399                continue;
2400            }
2401            if (sscanf(line, "image follows: %d %s %d %d\n", &numBytes, cacheId,
2402                       &frameNum, &rockOffset) != 4) {
2403                ERROR("can't get # bytes from \"%s\"", line);
2404                DEBUG("Leaving Writer thread");
2405                return NULL;
2406            }
2407#if WRITE_DEBUG
2408            DEBUG("found image line \"%.*s\"", numBytes, line);
2409#endif
2410            sprintf(header, "nv>image %d %s %d %d\n", numBytes, cacheId,
2411                    frameNum, rockOffset);
2412            hdrLength = strlen(header);
2413#if WRITE_DEBUG
2414            DEBUG("Queueing image numBytes=%d cacheId=%s, frameNum=%d, rockOffset=%d size=%d\n", numBytes, cacheId, frameNum, rockOffset, numBytes + hdrLength);
2415#endif
2416            imgPtr = NewImage(&list, numBytes + hdrLength);
2417            memcpy(imgPtr->data, header, hdrLength);
2418            if (ReadFollowingData(bp, imgPtr->data + hdrLength,
2419                        (size_t)numBytes) != BUFFER_OK) {
2420                ERROR("can't read %d bytes for \"image follows\" buffer: %s",
2421                      numBytes, strerror(errno));
2422#if WRITE_DEBUG
2423                DEBUG("Leaving Writer thread");
2424#endif
2425                return NULL;
2426            }
2427            stats.numFrames++;
2428            stats.numBytes += numBytes;
2429            {
2430                struct timeval tv;
2431                fd_set writeFds;
2432                int fd;
2433
2434                tv.tv_sec = tv.tv_usec = 0L;
2435                FD_ZERO(&writeFds);
2436                fd = fileno(stdout);
2437                FD_SET(fd, &writeFds);
2438                if (select(fd+1, NULL, &writeFds, NULL, &tv) > 0) {
2439                    WriteImages(&list, fd);
2440                }
2441            }
2442        }
2443    }
2444#if WRITE_DEBUG
2445    DEBUG("Leaving Writer thread");
2446#endif
2447    return NULL;
2448}
2449
2450int
2451main(int argc, char **argv)
2452{
2453    PymolProxy proxy;
2454    pthread_t thread1, thread2;
2455
2456    sprintf(stderrFile, "/tmp/pymol%d.stderr", getpid());
2457    fdebug = stderr;
2458    if (debug) {
2459        fdebug = fopen("/tmp/pymolproxy.log", "w");
2460    }   
2461    frecord = NULL;
2462    if (recording) {
2463        char fileName[200];
2464
2465        sprintf(fileName, "/tmp/pymolproxy%d.py", getpid());
2466        frecord = fopen(fileName, "w");
2467    }   
2468    fprintf(stdout, "PyMol 1.0\n");
2469    fflush(stdout);
2470
2471    INFO("Starting pymolproxy (threaded version)");
2472
2473    InitProxy(&proxy, stderrFile, argv + 1);
2474    if (pthread_create(&thread1, NULL, &ClientToServer, &proxy) < 0) {
2475        ERROR("Can't create reader thread: %s", strerror(errno));
2476    }
2477    if (pthread_create(&thread2, NULL, &ServerToClient, &proxy.server) < 0) {
2478        ERROR("Can't create writer thread: %s", strerror(errno));
2479    }
2480    if (pthread_join(thread1, NULL) < 0) {
2481        ERROR("Can't join reader thread: %s", strerror(errno));
2482    }
2483    return FreeProxy(&proxy);
2484}
Note: See TracBrowser for help on using the repository browser.