source: branches/nanovis2/packages/vizservers/pymolproxy/pymolproxy2.c @ 3305

Last change on this file since 3305 was 3305, checked in by ldelgass, 12 years ago

sync with trunk

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