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

Last change on this file since 3330 was 3330, checked in by gah, 11 years ago

merge (by hand) with Rappture1.2 branch

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