/*#########################################################################*/
/*
/*  Pseudo-index:
/*      1)  Initial blurb and dire warnings
/*      2)  Command interface documentation
/*      3)  General outline of the code
/*      4)  VAX Command Definition Language .CLD file for command line
/*      5)  VAX C Code
/*
/*  Generic info: VAX C V3.1, VMS 5.3, VAXNotes 2.0
/*
/*  date:         9. April, 1990
/*
/*#########################################################################*/

/*#########################################################################*/
/*
/*  This MERGE_NOTES utility is implemented using the DCL CLI interface and
/*  Callable notes interface.
/*
/*  IMPORTANT:  Several things are unsupported here:
/*
/*     1) Callable Notes is unsupported and subject to change without
/*        any notice at the whim of Digital.  No confidential information has
/*        been used to determine the correct item codes and values, just hex
/*        dumps, time, and dumb luck.
/*
/*     2) I'm at version 2.0 of Notes.  At this revision, the
/*        SET CONFERENCE/REPLY_ONLY command is unsupported.  Nonetheless,
/*        it is included here.
/*
/*     3) Most of all, this code is unsupported.  It is meant as an educational
/*        curiousity, not a production program.  To the best of my knowledge
/*        it does what it claims to do (at least on my system), but neither
/*        the author nor his employer make any representations as to the
/*        efficacy, readability, and or/safety of this program.  Anyone
/*        interested in a production quality system should rewrite this entire
/*        project so they can assure themselves of exactly what's going on.
/*
/*  That important required diversion said, it works well form me, and I
/*  hope you find this informative and useful.  If you make extensions and
/*  corrections, I would love to hear of them.  I do ask, however, that if
/*  in spite of the dire warnings above you use this code as the basis of
/*  your development that you leave in the warnings for future generations
/*  and my name as originator/contact.
/*
/*      Jon Jones
/*      Manager, DP Technical Support
/*      Quad/Graphics, Inc.
/*      Highway 74
/*      Sussex, WI  52089
/*      (414) 246-9200
/*
/*#########################################################################*/

/*#########################################################################*/
/*
/*  The WEAVE command is used to merge two VAXNotes Conferences on a topic
/*  by topic basis.  All reply notes remain attached to their original topics.
/*  The output conferences is ordered by creation date of the topic notes.
/*  Original authors and creation times are preserved for all notes.
/*
/*  Format:
/*     WEAVE/global-quals input-conf-1/input-quals input-conf-2/input-quals -
/*                        output-conf/output-quals
/*
/*  where:
/*     input-conf-n     is a VAXNotes notefile in its filename form (the user's
/*                      NOTES$NOTEBOOK.NOTE is ignored).
/*     output-conf-n    is a new VAXNotes notefile in its filename form.
/*
/* Global qualifiers:
/*    /LOG              produce counts for all things copied:  notes, keywords,
/*                      and member records.
/*    /LOCATION         a default location for all conference parameters.
/*                      defaults to NOTES$LIBRARY
/*
/* Input qualifiers:
/*    /NOMEMBERS        do NOT copy member records for this conference
/*    /NOKEYWORDS       do NOT copy keywords for this conference, or attach
/*                      them to any note copied from this conference
/*
/* Output qualifiers:
/*    /MEMBER_RESTRICT  output conference will have access restrictions
/*                      (equivalent of SET CONFERENCE/RESTRICT=MEMBER )
/*    /KEYWORD_RESTRICT output conference will have keyword create restrictions
/*                      (equivalent of SET CONFERENCE/RESTRICT=KEYWORD )
/*    /NOWRITE          readonly conference (must have moderator or write
/*                      regardless privileges to do anything but read notes).
/*    /REPLY_ONLY       new topics may not be written, but existing notes may
/*                      be replied to.  those with privilege may, of course,
/*                      do what they want
/*    /TITLE="title"    set the output conference's title
/*    /NOTICE="notice"  set the output conference's notice line
/*
/*    any other alterations must be made by a moderator.
/*
/*    NOTE *** the username running the WEAVE commands automatically becomes
/*         *** a moderator of the new conference.
/*
/*#########################################################################*/

/*#########################################################################*/
/*
/*  the following is a conglomerate of almost everything needed for this
/*  merge notes conference.  logically it consists of several files:
/*
/*       LOCAL_DEFS.H
/*       MN_DEFS.H
/*       MN_MAIN.C
/*       MN_CONFERENCES.C
/*       MN_KEYWORDS.C
/*       MN_MEMBERS.C
/*       MN_NOTES.C
/*       MN_UTILS.C
/*
/*  these are included in one file here for convenience of transfer.  it can
/*  be compiled as is (i'm using VAX C V3.1 under VMS 5.3) or broken out into
/*  it's logical components.
/*
/*  i do assume that you've extracted the file NOTES$ITEMDEF.H from the
/*  VAXNotes distribution and placed it in SYS$SHARE with the other .H files.
/*
/*  last, and not least, the next comment block contains the .CLD file to
/*  define a command (i call it WEAVE) to use the executable.  it should be
/*  extracted, uncommented, edited for location, and used with SET COMMAND.
/*
/*#########################################################################*/

/*#########################################################################
!
! file --> WEAVE.CLD
!
!   define WEAVE to merge two VAXNotes Conferences
!
define verb WEAVE
    image JON:[NOTES.MN]MERGE_NOTES
        parameter P1
            label = IN1
            prompt = "First input conference"
            value( required, type=$FILE )
        parameter P2
            label = IN2
            prompt = "Second input conference"
            value( required, type=$FILE )
        parameter P2
            label = OUT
            prompt = "Output conference"
            value( required, type=$FILE )
!
!  global qualifiers
!
        qualifier LOCATION			! default location for the
            nonnegatable			! notefiles.
            default
            value( type=$DIRECTORY, default="NOTES$LIBRARY:" )
        qualifier LOG				! produce counts at the end
!
!  input conference qualifiers
!
        qualifier NOMEMBERS			! don't copy membership info
            nonnegatable
            placement=local
        qualifier NOKEYWORDS			! don't copy keyword info
            nonnegatable
            placement=local
!
!  output conference qualifiers
!
        qualifier NOTICE			! conference notice string
            nonnegatable
            placement=local
            value( required, type = $QUOTED_STRING )
        qualifier TITLE				! conference header
            nonnegatable
            placement=local
            value( required, type = $QUOTED_STRING )
        qualifier MEMBER_RESTRICT		! access restricted
            nonnegatable
            placement=local
        qualifier KEYWORD_RESTRICT		! keyword create restricted
            nonnegatable
            placement=local
        qualifier REPLY_ONLY			! no write, replies allowed
            nonnegatable
            placement=local
        qualifier NOWRITE			! no write, no replies allowed
            nonnegatable
            placement=local
/*#########################################################################*/

/*#########################################################################*/
/*
/*  ---> from a local site generic header file (should be included in 
/*       MN_DEFS.H)
/*
/*#########################################################################*/

/*
 *  a fairly typical definition of the ITEM LIST 3 VMS data type suitable for
 *  most situations.  TERMINATE can be used in initialization to terminate a
 *  declare item list.
 */
   typedef struct {
      short length;
      short opcode;
      char  *buffer;
      char  *buflen;
   } ITEM_3;
#   define TERMINATE 0,0,0,0
#   define ITEM_LOAD( item, ln, op, bf, bl )				\
       item.length = ln; item.opcode = op; item.buffer = bf; item.buflen = bl

/*
 *  common responses to rtl and system service calls:
 */
#   define ODD( what )			( (what) & 1 )
#   define EVEN( what )			!ODD( what )

#   define CHECKR( what )		if ( EVEN( what ) ) return( what )
#   define CHECKX( what )		if ( EVEN( what ) ) exit  ( what )
#   define CHECKA( what, action )	if ( EVEN( what ) ) action

/*#########################################################################*/
/*
/*  MN_DEFS.H project (MERGE_NOTES) specific header file
/*
/*#########################################################################*/

#include <notes$itemdef>

#define LOG	1
#define LOGGED 		flags & LOG
#define PL		if ( LOGGED ) printf

#define STRING_DATA( str, len, max )    char str[ max ]; long len = 0
#define LONG_DATA( lng )                unsigned long lng = 0
#define DATE_DATA( dat )                unsigned long dat[ 2 ]

/*
 *  a couple of very useful generic item lists
 */

ITEM_3  null_list[] = { TERMINATE };
ITEM_3  next[]      = { 0, NOTES$K_NOSIGNAL, 0, 0, 
                        0, NOTES$K_CONTINUE, 0, 0, 
                        TERMINATE };

/*
 * notes and conference data types
 */

struct msg_info {
   unsigned long date[2];		/* create date */
   unsigned long uid;			/* binary id   */
   char id[17];				/* string id   */
};

struct conf_info {
   unsigned long date[2];
   unsigned long cnfctx;		/* notefile context		     */
   unsigned long msgctx;		/* message (note) context	     */
   unsigned long memctx;		/* member (user) context	     */
   unsigned long keyctx;		/* keyword context		     */
   struct msg_info *cmap;		/* sorted list of topic notes(by date)*/
   unsigned long maxmsg;                /* number of topics in input conf    */
   unsigned long finfo;                 /* rms file info                     */
   char purported_name[ 256 ];          /* name from command line            */
   char name[ 256 ];                    /* real name                         */
   char isoutput;                       /* type of conference                */
   char nomember;                       /* don't copy members  -- input only */
   char nokeyword;                      /* don't copy keywords -- input only */
   char nowrite;                        /* restrict output conf writers-total*/
   char replyonly;                      /* restrict output conf writers      */
   char member;                         /* restrict output conf members      */
   char keyword;                        /* restrict output conf keyworders   */
   long memcnt;				/* number of members		     */
   long keycnt;				/* number of keywords		     */
   long thdcnt;				/* number of threads		     */
   long msgcnt;				/* number of notes		     */
   char title[ 256 ];			/* title for an output conference    */
   char notice[ 256 ];			/* notice for an output conference   */
};

/*#########################################################################*/
/*
/*  MN_MAIN --> mainline routine:  crack command line, do initializing, 
/*  perform execution loop and exit.
/*
/*#########################################################################*/

main()
{
   char defdir[ 256 ];
   long status;
   struct conf_info a, b, c;
   long flags = 0;
   long i = 0, j = 0;

   crack_line( &a, &b, &c, defdir, &flags );

   status = open_conference( defdir, &a, flags );
   CHECKX( status );
   status = open_conference( defdir, &b, flags );
   CHECKX( status );
   status = open_conference( defdir, &c, flags );
   CHECKX( status );

   copy_stuff( &a, &c, flags );
   copy_stuff( &b, &c, flags );

   load_arrays( &a );
   load_arrays( &b );

   while ( i < a.maxmsg && j < b.maxmsg ) {
      if ( cut_cmptim( (a.cmap)[ i ].date, (b.cmap)[ j ].date ) < 0 )
         copy_thread( &(a.cmap)[ i++ ], &a, &c );
      else
         copy_thread( &(b.cmap)[ j++ ], &b, &c );
   }

   while ( i < a.maxmsg )
      copy_thread( &(a.cmap)[ i++ ], &a, &c );

   while ( j < b.maxmsg )
      copy_thread( &(b.cmap)[ j++ ], &b, &c );

   close_conference( &a, flags );
   close_conference( &b, flags );
   close_conference( &c, flags );
}

crack_line( a, b, c, defdir, flags )
   char *defdir;
   struct conf_info *a, *b, *c;
   long *flags;
{
   long status;
   char tp[ 256 ];

/*
 *  global qualifiers
 */
   if ( EVEN( get_value( "LOCATION", tp ) ) ) tp[0] = 0;
   sprintf( defdir, "%s.NOTE", tp );
   if ( ODD( present( "LOG" ) ) )   *flags |= LOG;

/*
 *  get the parameters and their qualifiers
 */
   init_conf( a, "IN1", 0 );
   init_conf( b, "IN2", 0 );
   init_conf( c, "OUT", 1 );
}

init_conf( conf, pname, isout )
   struct conf_info *conf;
   char *pname;
   long isout;
{
   memset( conf, 0, sizeof( struct conf_info ) );	/* clear it out */
   get_value( pname, conf->purported_name );		/* get name */
   conf->isoutput  = isout;
   if ( conf->isoutput ) {				/* proc output quals */
      conf->nowrite   = ODD( present( "NOWRITE" ) );
      conf->replyonly = ODD( present( "REPLY_ONLY" ) );
      conf->member    = ODD( present( "MEMBER_RESTRICT" ) );
      conf->keyword   = ODD( present( "KEYWORD_RESTRICT" ) );
      if ( EVEN( get_value( "TITLE", conf->title ) ) )
         conf->title[ 0 ] = 0;
      if ( EVEN( get_value( "NOTICE", conf->notice ) ) )
         conf->notice[ 0 ] = 0;
   }
   else {						/* proc input quals */
      conf->nomember  = ODD( present( "NOMEMBERS" ) );
      conf->nokeyword = ODD( present( "NOKEYWORDS" ) );
   }
}

present( str )					/* convenient stub */
   char *str;
{
   long d[] = { strlen( str ), str };
   return CLI$PRESENT( d );
}

get_value( str, ret )				/* convenient stub */
   char *str, *ret;
{
   long d1[] = { strlen( str ), str };
   long d2[] = { 255, ret };
   long rlen = 0;
   long status = CLI$GET_VALUE( d1, d2, &rlen );
   if ( status & 1 ) ret[ rlen ] = 0;
   return status;
}

copy_stuff( src, dst, flags )
   struct conf_info *src, *dst;
   long flags;
{
   copy_keyword( src, dst, flags );
   copy_members( src, dst, flags );
}

/*#########################################################################*/
/*
/*  MN_CONFERENCES.C --> handle the conferences
/*
/*#########################################################################*/

/*************************************************************************
 *
 *  open conference must open or create the given conference; establish
 *  message, user, and keyword context for the conference; and if this is
 *  a creation, open an rms channel to the file.  any error in here must
 *  be considered fatal to the entire merge process.
 *
 *************************************************************************/

open_conference( defdir, conf, flags )
   char *defdir;
   struct conf_info *conf;
   long flags;
{
   long status;
   long len    = 0;
   long retval = 1;
   long i;

   ITEM_3 conf_open[] = {
      0, NOTES$K_NOSIGNAL, 0, 0,
      strlen( conf->purported_name ), NOTES$K_NOTEFILE_FILE_NAME, 
                                                    conf->purported_name , 0,
      strlen( defdir ), NOTES$K_NOTEFILE_DEFAULT_NAME, defdir, 0,
      TERMINATE
   };

   long zero = 0, one = 1;
#define CBASE 6				/* must match the first TERMINATE */
   ITEM_3 conf_create[] = {
      0,                 NOTES$K_NOSIGNAL,              0, 0,
      0,                 NOTES$K_NOTEFILE_CREATE ,      0, 0,
      strlen( conf->purported_name ), NOTES$K_NOTEFILE_FILE_NAME, 
                                                    conf->purported_name , 0,
      strlen( defdir ), NOTES$K_NOTEFILE_DEFAULT_NAME, defdir, 0,
      strlen( conf->title ), NOTES$K_NOTEFILE_TITLE, conf->title, 0,
      strlen( conf->notice ), NOTES$K_NOTEFILE_NOTICE, conf->notice, 0,
      TERMINATE,
      TERMINATE,			/* extra terminates in case we */
      TERMINATE,			/* have to add some more item  */
      TERMINATE				/* codes due to qualifiers.    */
   };

   ITEM_3 conf_out[] = {
      255, NOTES$K_NOTEFILE_RESULT_SPEC, conf->name,    &len,
        8, NOTES$K_NOTEFILE_CREATE_TIME, conf->date,    0,
        4, NOTES$K_NOTEFILE_NUMNOTES,    &conf->maxmsg, 0,
      TERMINATE
   };
   ITEM_3 start[] = {
      0, NOTES$K_NOSIGNAL, 0, 0,
      4, NOTES$K_NOTEFILE_CONTEXT, &conf->cnfctx, 0,
      TERMINATE
   };

   if ( conf->isoutput ) {
      i = CBASE;								/* setup any output parameters */
      if ( conf->nowrite ) {
         ITEM_LOAD( conf_create[ i ], 4, NOTES$K_NOTEFILE_WRITELOCK, &one, 0 );
         i++;
      }
      else if ( conf->replyonly ) {
         ITEM_LOAD( conf_create[ i ], 4, NOTES$K_NOTEFILE_REPLY_ONLY, &one, 0 );
         i++;
      }
      if ( conf->member ) {
         ITEM_LOAD( conf_create[ i ], 4, NOTES$K_NOTEFILE_RESTRICTED, &one, 0 );
         i++;
      }
      if ( conf->keyword ) {
         ITEM_LOAD( conf_create[ i ], 4, NOTES$K_USER_CREATE_KEYWORD, &zero, 0 );
      }
      else {
         ITEM_LOAD( conf_create[ i ], 4, NOTES$K_USER_CREATE_KEYWORD, &one, 0 );
      }

      status = NOTES$NOTEFILE_BEGIN( &conf->cnfctx, conf_create, conf_out );		/* create new conference */
   }
   else
      status = NOTES$NOTEFILE_BEGIN( &conf->cnfctx, conf_open, conf_out );		/* open existing conference */
   CHECKR( status );
   conf->name[ len ] = 0;

   status = NOTES$KEYWORD_BEGIN( &conf->keyctx, start, null_list);
   CHECKR( status );
   status = NOTES$USER_BEGIN(    &conf->memctx, start, null_list);
   CHECKR( status );
   status = NOTES$NOTE_BEGIN(    &conf->msgctx, start, null_list);
   CHECKR( status );

   if ( conf->isoutput )  {
      conf->finfo = rms_open( conf->name, "w+" ) ;
      if ( !conf->finfo ) retval = rms_last_error();
   }
   else conf->finfo = 0;

   return retval;
}

/*************************************************************************
 *
 *  close conference reverse all of open_conference's operations
 *
 *************************************************************************/

close_conference( conf, flags )
   struct conf_info *conf;
   long flags;
{
   char tp[ 32 ];

   if ( conf->finfo )								/* close new conf as a file */
      rms_close( conf->finfo );

   NOTES$KEYWORD_END(  &conf->keyctx, null_list, null_list );			/* detach all contexts */
   NOTES$USER_END(     &conf->memctx, null_list, null_list );
   NOTES$NOTE_END(     &conf->msgctx, null_list, null_list );
   NOTES$NOTEFILE_END( &conf->cnfctx, null_list, null_list );
   if ( LOGGED ) {
      strcpy( tp, ( conf->isoutput ) ? "Created" : "Copied" );
      printf("\nConference:     %s\n", conf->name );
      printf("   Members:       %s %d\n", tp, conf->memcnt );
      printf("   Keywords:      %s %d\n", tp, conf->keycnt );
      printf("   Threads/Notes: %s %d/%d\n", tp, conf->thdcnt, conf->msgcnt );
   }
}

/*#########################################################################*/
/*
/*  MN_KEYWORDS.C -- handle keyword copying
/*
/*#########################################################################*/

/*************************************************************************
 *
 *  copy_keywords moves keywords from conference src to conference dst.  if
 *  the keyword already exists, then the definition is not overidden.  
 *
 *************************************************************************/

copy_keyword( src, dst, flags )
   struct conf_info *src, *dst;
   long flags;
{
   long status;

   STRING_DATA( buf, buflen, 255 );					/* places to hold keyword info */
   STRING_DATA( usr, usrlen, 255 );

   ITEM_3 first[] = {							/* Initialize item lists */
      0, NOTES$K_NOSIGNAL, 0, 0,
      1, NOTES$K_KEYWORD_NAME, "*", 0,
      TERMINATE
   };
   ITEM_3 fetch[] = {
      255, NOTES$K_KEYWORD_NAME,      buf, &buflen ,
      255, NOTES$K_KEYWORD_USER_AREA, usr, &usrlen ,
      TERMINATE
   };

#define	NAME 1					/* these gotta match the item */
#define USER 2					/* list that follows */
   ITEM_3 put[] = {
        0, NOTES$K_NOSIGNAL,            0, 0,
      255, NOTES$K_KEYWORD_NAME,      buf, 0,
      255, NOTES$K_KEYWORD_USER_AREA, usr, 0, 
      TERMINATE
   };
/*
 *  Start
 */
   if ( !src->nokeyword ) {
      status = NOTES$KEYWORD_GET( &src->keyctx, first, fetch );			/* get first keyword */
      while ( status & 1 ) {
         put[ NAME ].length = buflen;						/* fixup output item-list */
         put[ USER ].length = usrlen;
         if ( ODD( NOTES$KEYWORD_ADD( &dst->keyctx, put, null_list ) ) ) {	/* output keyword, don't care if successful */
            src->keycnt++;							/* except for statistics */
            dst->keycnt++;
         }
         status = NOTES$KEYWORD_GET( &src->keyctx, next, fetch );		/* get next keyword */
      }
   }
}

/*#########################################################################*/
/*
/*  MN_MEMBERS.C --> handle member copying
/*
/*#########################################################################*/

/*************************************************************************
 *
 *  copy_members moves members from conference src to conference dst.  if
 *  the member already exists, then the definition is not overidden.  this
 *  has the side effect that the person running the merge becomes a moderator.
 *
 *************************************************************************/

copy_members( src, dst )
   struct conf_info *src, *dst;
{
   long status;

   STRING_DATA( nam, namlen, 255 );   /* things to hold member information */
   STRING_DATA( nod, nodlen, 255 );
   STRING_DATA( acc, acclen, 255 );
   STRING_DATA( mai, mailen, 255 );
   STRING_DATA( usr, usrlen, 255 );

   LONG_DATA( create );
   LONG_DATA( moderate );
   LONG_DATA( bypass );

   ITEM_3 first[] = {                   /*  initialize item lists */
      0, NOTES$K_NOSIGNAL, 0, 0,
      1, NOTES$K_USER_NAME, "*", 0,
      TERMINATE
   };

   ITEM_3 fetch[] = {
      255, NOTES$K_USER_NAME,           nam,       &namlen,
      255, NOTES$K_USER_NODENAME,       nod,       &nodlen,
      255, NOTES$K_USER_ACCESS_LIST,    acc,       &acclen,
      255, NOTES$K_USER_MAIL_ADDR,      mai,       &mailen,
      255, NOTES$K_USER_USER_AREA,      usr,       &usrlen,
      4,   NOTES$K_USER_CREATE_KEYWORD, &create,   0,
      4,   NOTES$K_USER_MODERATE,       &moderate, 0,
      4,   NOTES$K_USER_WRITE_BYPASS,   &bypass,   0,
      TERMINATE
   };

#define USERNAME                1       /* make sure that these match the */
#define NODENAME                2       /* following item list EXACTLY    */
#define ACCESS                  3
#define MAILADDR                4
#define USERAREA                5

   ITEM_3 put[] = {
      0, NOTES$K_NOSIGNAL,              0,       0,
      0, NOTES$K_USER_NAME,           nam,       0,
      0, NOTES$K_USER_NODENAME,       nod,       0,
      0, NOTES$K_USER_ACCESS_LIST,    acc,       0,
      0, NOTES$K_USER_MAIL_ADDR,      mai,       0,
      0, NOTES$K_USER_USER_AREA,      usr,       0,
      4, NOTES$K_USER_CREATE_KEYWORD, &create,   0,
      4, NOTES$K_USER_MODERATE,       &moderate, 0,
      4, NOTES$K_USER_WRITE_BYPASS,   &bypass,   0,
      TERMINATE
   };
/*
 *  Start: get the first member
 */   
   if ( !src->nomember ) {
      status = NOTES$USER_GET( &src->memctx, first, fetch );
      while ( status & 1 ) {
         put[ USERNAME ].length = namlen;  /* finish up the output item-list */
         put[ NODENAME ].length = nodlen;
         put[ ACCESS ].length   = acclen;
         put[ MAILADDR ].length = mailen;
         put[ USERAREA ].length = usrlen;
         if ( ODD( NOTES$USER_ADD( &dst->memctx, put, null_list ) ) ) {   /* output member */
            src->memcnt++;						  /* update stats  */
            dst->memcnt++;
         }
         status = NOTES$USER_GET( &src->memctx, next, fetch ); /* get next membr */
      }
   }
}

/*#########################################################################*/
/*
/*  MN_NOTES.C --> copy the threads and add keywords.
/*
/*#########################################################################*/

/*************************************************************************
 *
 *  load_arrays allocates an array to store creation time information for
 *  all the topic notes in the conference, then calls topic info repeatedly
 *  to populate it.  it then sorts the array by creation time;
 *
 *************************************************************************/

load_arrays( conf )
   struct conf_info *conf;
{
   long i, note_compare();
   char first = 1;

   if ( conf->maxmsg > 0 ) {								/* skip it if there are no notes */
      conf->cmap = calloc( conf->maxmsg, sizeof( struct msg_info ) );			/* create array */
   
      for( i = 0; i < conf->maxmsg; i++ )						/* populate array */
         topic_info( &(conf->cmap)[ i ], conf, &first );
   
      qsort( conf->cmap, conf->maxmsg, sizeof( struct msg_info ), note_compare );	/* sort array */
   }
}

note_compare( a, b )									/* sort routine for qsort */
   struct msg_info  *a, *b;
{
   return( cut_cmptim( a->date, b->date ) );
}

/*************************************************************************
 *
 *  topic_info gets the creation date and string id of the next topic note
 *  in the conference.  things work slightly differently the first time around.
 *
 *************************************************************************/

topic_info( topic, conf, firsttime )
   struct conf_info *conf;
   struct msg_info  *topic;
   char *firsttime;
{
   long status;
   char *p;

   ITEM_3 first[] = {
      0, NOTES$K_NOSIGNAL, 0, 0,
      1, NOTES$K_NOTE_ID, "*", 0,
      TERMINATE
   };
   ITEM_3 topic_stuff[] = {
      16, NOTES$K_NOTE_ID,          topic->id,   0,
       8, NOTES$K_NOTE_CREATE_TIME, topic->date, 0,
      TERMINATE
   };

   status = NOTES$NOTE_GET( &conf->msgctx,   ( (*firsttime) ? first : next ),
			    topic_stuff );
   if ( ODD( status ) ) {
      for ( p = topic->id; *p != '.'; p++ );					/* convert id into wildcard id; e.g. 1.0 -> 1.* */
      *++p = '*';
      *++p = 0;
   }
   *firsttime = 0;
}

/*************************************************************************
 *
 *  copy_thread does that:  copies a topic and all replies from conference
 *  "from" to conference "to"  it copies all attributes of the note:  the
 *   text, the keywords, any read/write info, etc.  the tricky part is this:
 *   NOTES will auromatically make the person running this programmer the
 *   author of the note and give it a creation time of now.  alter_note
 *   must be called in order the straighten this situation out by brute force.
 *
 *************************************************************************/

copy_thread( what, from, to )
   struct msg_info *what;
   struct conf_info *from, *to;
{
   long status;
   long i;

   STRING_DATA( newid,    newlen,   15 );					/* id of the new note */
   STRING_DATA( title,    titlen,  255 );					/* title of note */
   STRING_DATA( author,   autlen,   32 );					/* author's name */
   STRING_DATA( usr,      usrlen,  255 );					/* user data area */
   STRING_DATA( pen,      penlen,  255 );					/* pen name of author */
   STRING_DATA( textline, texlen, 1024 );					/* text line */
   STRING_DATA( keyword,  keylen,  255 );					/* keyword name */

   LONG_DATA( hidden );								/* note is hidden flag */
   LONG_DATA( writelock );							/* note is readonly flag */
   LONG_DATA( nrecs );								/* number of text lines in the note  */
   LONG_DATA( uid );								/* note unique id (rms primary key) */

   DATE_DATA( ctime );								/* creation time of the note */

   ITEM_3 first[] = {								/* to get the first note in thread */
      0,                  NOTES$K_NOSIGNAL,              0,        0,
      strlen( what->id ), NOTES$K_NOTE_ID,               what->id, 0,		/* this is wild-card formatted     */
      0,                  NOTES$K_NOTE_HINT_GET_TEXT,    0,        0,		/* we'll be getting text later     */
      0,                  NOTES$K_NOTE_HINT_GET_KEYWORD, 0,        0,		/* we'll be getting keywords later */
      TERMINATE
   };
   ITEM_3 next_note[] = {							/* to get the next note in thread */
      0, NOTES$K_NOSIGNAL,              0, 0,
      0, NOTES$K_NOTE_HINT_GET_TEXT,    0, 0,					/* we'll be getting text later     */
      0, NOTES$K_NOTE_HINT_GET_KEYWORD, 0, 0,					/* we'll be getting keywords later */
      0, NOTES$K_CONTINUE,              0, 0,					/* get next in wildcard sequence   */
      TERMINATE
   };
   ITEM_3 topic_stuff[] = {							/* to get fetch note-wide info */
       32, NOTES$K_NOTE_AUTHOR,      author,     &autlen,
        8, NOTES$K_NOTE_CREATE_TIME, ctime,            0,
        4, NOTES$K_NOTE_HIDDEN,      &hidden,          0,
      255, NOTES$K_NOTE_PEN_NAME,    pen,        &penlen,
      255, NOTES$K_NOTE_TITLE,       title,      &titlen,
        4, NOTES$K_NOTE_WRITELOCK,   &writelock,       0,
      255, NOTES$K_NOTE_USER_AREA,   usr,        &usrlen,
        4, NOTES$K_NOTE_NUMRECORDS,  &nrecs,           0,
      TERMINATE
   };

#define	PEN_NAME		2						/* these MUST match the positions in the */
#define TITLE			3						/* topic_out_1 and topic_out_2 itemlists */
#define	USER_AREA		5						/* (which MUST be identical, except for  */
#define BLINK			6						/* BLINK)				 */

   ITEM_3 topic_out_1[] = {
        0, NOTES$K_NOSIGNAL,               0,    0,				/* to write topic notes */
        4, NOTES$K_NOTE_HIDDEN,      &hidden,    0,
        0, NOTES$K_NOTE_PEN_NAME,    pen,        0,
        0, NOTES$K_NOTE_TITLE,       title,      0,
        4, NOTES$K_NOTE_WRITELOCK,   &writelock, 0,
        0, NOTES$K_NOTE_USER_AREA,   usr,        0,
        TERMINATE
   };
   ITEM_3 topic_out_2[] = {							/* to write replies */
        0, NOTES$K_NOSIGNAL, 0, 0,
        4, NOTES$K_NOTE_HIDDEN,      &hidden,    0,
        0, NOTES$K_NOTE_PEN_NAME,    pen,        0,
        0, NOTES$K_NOTE_TITLE,       title,      0,
        4, NOTES$K_NOTE_WRITELOCK,   &writelock, 0,
        0, NOTES$K_NOTE_USER_AREA,   usr,        0,
        0, NOTES$K_NOTE_BLINK_ID,    newid,      0,
        TERMINATE
   };
   ITEM_3 *topic_out = topic_out_1;						/* first list to process */

   ITEM_3 grab_id[] = {								/* to get the output conference ids   */
      15, NOTES$K_NOTE_ID, newid, &newlen,					/* string id--used to add keywords    */
       4, NOTES$K_NOTE_UID, &uid, 0, 						/* binary id--used for rms alteration */
      TERMINATE
   };

/*
 *  item lists necessary for processing text lines
 */
   ITEM_3 text_hold[] = {							/* get a text line */
      1024, NOTES$K_TEXT_STRING, textline, &texlen,
      TERMINATE
   };
#define TEXT_LINE		1						/* MUST be array index of NOTES$K_TEXT_STRING */
   ITEM_3 text_line[] = {							/* write a text line */
      0, NOTES$K_NOSIGNAL, 0, 0,
      0, NOTES$K_TEXT_STRING, textline, 0,
      TERMINATE
   };
   ITEM_3 text_end[] = {							/* close up text processing */
      0, NOTES$K_NOSIGNAL, 0, 0,
      0, NOTES$K_TEXT_END, 0, 0,
      TERMINATE
   };

/*
 *  item lists necessary for processing key words
 */
   ITEM_3 key_hold[] = {							/* get a keyword */
      255, NOTES$K_KEYWORD_NAME, keyword, &keylen,
      TERMINATE
   };
#define KEY_NAME		1						/* MUST be array index of NOTES$K_KEYWORD_NAME */
#define KEY_ID			2						/* MUST be array index of NOTES$K_NOTES_ID */
   ITEM_3 key_line[] = {							/* add a keyword */
      0, NOTES$K_NOSIGNAL, 0, 0,
      0, NOTES$K_KEYWORD_NAME, keyword, 0,
      0, NOTES$K_NOTE_ID, newid, 0,
      TERMINATE
   };
/*
 *  the action begins
 */
   status = NOTES$NOTE_GET( &from->msgctx, first, topic_stuff );		/* get topic note */
   while ( ODD( status ) ) {
      topic_out[ PEN_NAME ].length  = penlen;					/* save output lengths  */
      topic_out[ TITLE ].length     = titlen;
      topic_out[ USER_AREA ].length = usrlen;
      status = NOTES$NOTE_ADD( &to->msgctx, topic_out, grab_id );		/* create new note with specified attributes */
      topic_out = topic_out_2;							/* (succeeding notes use the CONTINUE format */
      key_line[ KEY_ID ].length = topic_out[ BLINK ].length = newlen;		/* length of the new id (from output conf)   */
      if ODD( status ) {
         from->msgcnt++;
         to->msgcnt++;
         alter_note( to->finfo, uid, author, autlen, ctime );			/* fixup author/creation time */
         if ( !from->nokeyword ) {
            while ( ODD( NOTES$NOTE_GET_KEYWORD( &from->msgctx, next, key_hold ))) { /* get keywords one at a time */
                key_line[ KEY_NAME ].length = keylen;
                status = NOTES$KEYWORD_ADD( &to->keyctx, key_line, null_list);       /* crossreference keyword to this note */
            }
         }
         for ( i = 0; i < nrecs; i++ ) {
            status = NOTES$NOTE_GET_TEXT( &from->msgctx, next, text_hold );	/* copy the text */
            if ( ODD( status ) ) {
               text_line[ 1 ].length = texlen;
               status = NOTES$NOTE_ADD_TEXT( &to->msgctx, text_line, null_list );
            }
         }
         status = NOTES$NOTE_ADD_TEXT( &to->msgctx, text_end, null_list );	/* end text */
      }
      status = NOTES$NOTE_GET( &from->msgctx, next_note, topic_stuff );		/* get next note */
   }
   from->thdcnt++;
   to->thdcnt++;
}

/*************************************************************************
 *
 *  alter_note needs to go into the output conference file via rms and
 *  tweak the the file, replacing the author and creation date with the
 *  ones pulled from the input conference.
 *
 *************************************************************************/

alter_note( finfo, uid, who, wholen, when )
   unsigned long finfo, uid, when[2], wholen;
   char *who;
{
   char buf[1024], ubuf[1024];
   long bin = 0, bout = 0, status;

   status = rms_get_eql( finfo, buf, 1024, 0, &uid, 4, &bin ); 			/* find note header record */
   if ( ODD( status ) ) {
      transfer( who, wholen, when, buf, bin, ubuf, &bout );			/* copy record, replacing author/create-time */
      status = rms_update( finfo, ubuf, bout );					/* update the header record */
   }
   return status;
}

/*************************************************************************
 *
 *  transfer copies the header record from buf to ubuf, leaving keys unchanged,
 *  and copying all tag fields except for author and creation-date.  these to
 *  are replaced with the data sent in.
 *
 *************************************************************************/

transfer( who, wholen, when, buf, bin, ubuf, bout )
   unsigned char *who, *buf, *ubuf;
   long wholen, bin, *bout;
   unsigned long *when;
{
   unsigned char *p = buf, *q = ubuf, *t = &buf[ bin ];
   unsigned char tag1, tag2, *data;
   short length;
   memcpy( q, p, 76 );							/* copy the keys */
   q += 76;
   p += 76;
   while ( p < t ) {
      p = crack( p, &tag1, &tag2, &length, &data );
      switch ( tag1 ) {
         case 0xC0 + NOTES$K_NOTE_AUTHOR:				/* replace author */
            q = encode( q, tag1, tag2, wholen, who );
            break;
         case 0xC0 + NOTES$K_NOTE_CREATE_TIME:				/* replace creation time */
            q = encode( q, tag1, tag2, length, when );
            break;
         default:							/* copy anything else */
            q = encode( q, tag1, tag2, length, data );
            break;
      }
   }
   *bout = ( q - ubuf );						/* calculate output record length */
}

crack( p, t1, t2, len, data )
   unsigned char *p, *t1, *t2, **data;
   short *len;
{
   *t1 = *p++;
   *t2 = ( *t1 == 0xDF ) ? *p++ : 0;
   switch( *p ) {
      case 0x80:  *len = 0;    break;
      case 0x81:  *len = *++p; break;
      case 0x82:  *len = *++p; *len |= ( *p << 8 ); break;
      default:    *len = *p; break;
   }
   *data = ++p;
   return( p + *len );
}

encode( p, t1, t2, len, data )
   unsigned char *p, t1, t2, *data;
   short len;
{
   *p++ = t1;
   if ( t1 == 0xDF ) *p++ = t2;
   if ( len < 128 )
      *p++ = len;
   else if ( len < 256 ) {
      *p++ = 0x81;
      *p++ = len;
   }
   else { 
      *p++ = 0x82;
      *p++ = ( len & 0xFF );
      *p++ = ( len >> 8 );
   }
   memcpy( p, data, len );
   return( p + len );
}

/*#########################################################################*/
/*
/*  MN_UTILS.C --> some utility functions hacked out of some of our local
/*                 VAX C libraries.
/*
/*#########################################################################*/

cut_cmptim( t1, t2 )					/* compare two quadword times, ala strcmp, etc */
   unsigned long *t1, *t2;
{
   if ( t1 [ 1 ] == t2 [ 1 ] ) {
      if ( t1 [ 2 ] == t2 [ 2 ] ) return 0;
      else                        return ( t1[ 2 ] > t2 [ 2 ] ) ? 1 : -1;
   }
   else return ( t1[ 1 ] > t2 [ 1 ] ) ? 1 : -1;
}


/*
 *  an ungraceful but workable excision--replace with your own if you've gottem
 */

#include <stddef>
#include <rms>

unsigned long rms_status;

typedef struct rms_unit {
   unsigned long status;
   struct FAB	 *fab;
   struct RAB	 *rab;
   struct NAM	 *nam;
   char	  rsa[ NAM$C_MAXRSS ];
} RMS_UNIT;

rms_open( fname, access )
   char *fname, *access;
{
   RMS_UNIT *new;
/*
 *  allocate and initialize storage space for RMS_UNIT and the rms blocks.
 */
   new = malloc( sizeof( RMS_UNIT ) );
   if ( !new ) return NULL;

   new->fab = malloc( FAB$C_BLN );
   if ( !new->fab ) {
      free( new );
      return NULL;
   }
   memcpy( new->fab, &cc$rms_fab, FAB$C_BLN );

   new->rab = malloc( RAB$C_BLN );
   if ( !new->rab ) {
      free( new->fab );
      free( new );
      return NULL;
   }
   memcpy( new->rab, &cc$rms_rab, RAB$C_BLN );

   new->nam = malloc( NAM$C_BLN );
   if ( !new->nam ) {
      free( new->fab );
      free( new->rab );
      free( new );
      return NULL;
   }
   memcpy( new->nam, &cc$rms_nam, NAM$C_BLN );

/*
 *  load default values in...later it might get smarter.
 */
   new->fab->fab$l_fna = fname;
   new->fab->fab$b_fns = strlen( fname );
   new->fab->fab$l_nam = new->nam;
   if ( !strcmp( access, "w+" ) )
      new->fab->fab$b_fac = FAB$M_GET | FAB$M_DEL | FAB$M_PUT | FAB$M_UPD;
   else
      new->fab->fab$b_fac = FAB$M_GET;
   new->fab->fab$b_shr = FAB$M_SHRPUT | FAB$M_SHRGET | 
                                               FAB$M_SHRDEL | FAB$M_SHRUPD;

   new->rab->rab$l_fab = new->fab;

   new->nam->nam$l_rsa = new->rsa;
   new->nam->nam$b_rss = NAM$C_MAXRSS;

   new->status = sys$open( new->fab );
   if ( ODD( new->status ) ) {
      new->rsa[ new->nam->nam$b_rsl ] = 0;
      new->status = sys$connect( new->rab );
   }

   if ( EVEN( new->status ) ) {
      rms_status = new->status;
      free( new->fab );
      free( new->rab );
      free( new->nam );
      free( new );
      new = NULL;
   }

   return new;
}

rms_close( rms_id )
   RMS_UNIT *rms_id;
{
   sys$disconnect( rms_id->rab );
   sys$close( rms_id->fab );
   free( rms_id->fab );
   free( rms_id->rab );
   free( rms_id->nam );
   free( rms_id );
   return NULL;
}

rms_get_eql( rms_id, buffer, max, keyno, key, keylen, len )
   long rms_id, buffer, max, key, keyno, keylen, len;
{
   return rms_get_idx( rms_id, buffer, max, keyno, key, keylen, 0, len, 0 );
}

rms_get_idx( rms_id, buffer, max, keyno, key, keylen, match, len, flag )
   RMS_UNIT *rms_id;
   char     *buffer, *key, keyno;
   short    max, keylen, *len;
   long     match, flag;
{
   rms_id->rab->rab$l_ubf = buffer;
   rms_id->rab->rab$w_usz = max;
   rms_id->rab->rab$b_krf = keyno;
   rms_id->rab->rab$l_kbf = key;
   rms_id->rab->rab$b_ksz = keylen;
   rms_id->rab->rab$b_rac = RAB$C_KEY;

   switch ( match ) {
      case RAB$M_KGE:	rms_id->rab->rab$l_rop = flag | RAB$M_KGE; break;
      case RAB$M_KGT:	rms_id->rab->rab$l_rop = flag | RAB$M_KGT; break;
      default:		rms_id->rab->rab$l_rop = flag;		break;
   }
   
   rms_status = rms_id->status = sys$get( rms_id->rab );
   *len = rms_id->rab->rab$w_rsz;
   return rms_id->status;
}

rms_update( rms_id, buf, len )
   RMS_UNIT *rms_id;
   char     *buf;
   long     len;
{
   rms_id->rab->rab$l_rbf = buf;
   rms_id->rab->rab$w_rsz = len;
   rms_status = rms_id->status = SYS$UPDATE( rms_id->rab );
   SYS$RELEASE( rms_id->rab );
   return rms_status;
}

long rms_last_error()
{ 
   return rms_status;
}

