source: pymolproxy/trunk/pymolproxy.c @ 4592

Last change on this file since 4592 was 4592, checked in by ldelgass, 6 years ago

Remove old (non-threaded) pymolproxy implementation from trunk. Can be restored
to a branch if ever need it again. The trunk version of nanoscale will be
changed to use pymolproxy as the binary name

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