source: pymolproxy/trunk/pymolproxy.c @ 6612

Last change on this file since 6612 was 6612, checked in by ldelgass, 7 years ago

Add define to control if pymol stderr is logged, by default discard stderr.

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