source: pymolproxy/branches/1.0/pymolproxy.c @ 6606

Last change on this file since 6606 was 6606, checked in by ldelgass, 8 years ago

merge r6598:6605 from pymolproxy trunk

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