source: pymolproxy/tags/1.0.2/pymolproxy.c

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

merge from pymolproxy trunk

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