source: pymolproxy/trunk/pymolproxy.c @ 6599

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

minor cleanups

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