source: pymolproxy/trunk/pymolproxy.c @ 6398

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

Fix stats time entries

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