source: pymolproxy/trunk/pymolproxy.c @ 6128

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

whitespace

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