source: trunk/packages/vizservers/pymolproxy/pymolproxy2.c @ 3376

Last change on this file since 3376 was 3376, checked in by gah, 10 years ago

new version of stats file handling without file locking

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