home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / pmmrxtoo.zip / pmmrxtoo.cmd next >
OS/2 REXX Batch file  |  1997-11-05  |  42KB  |  1,102 lines

  1. /*
  2. program: pmmrxtoo.cmd
  3. type:    REXXSAA-OS/2, Object Rexx, REXXSAA 6.x
  4. purpose: utilities for dealing with LaMail and PMMail files
  5. version: 0.1.0
  6. date:    1997-02-10
  7. changed: 1997-11-05, ---rgf, determining whether PM-Mail is running, aborts if so
  8.  
  9. author:  Rony G. Flatscher
  10.          Rony.Flatscher@wu-wien.ac.at
  11.  
  12. needs:   ObjectRexx and REQUIRES-files
  13.  
  14. usage:   pmmrxtoo d, or via a call or ::require
  15.  
  16.          pmmrxtoo 
  17.             ... without an option merely returns (so it and its classes can be 
  18.                 required without starting any actions)
  19.          
  20.          pmmrxtoo /option1 [account] [/go]:
  21.  
  22.            /s [account] [/go]   ... sort files          ... option = 1
  23.            /d [account] [/go]   ... dump files          ... option = 2
  24.            /h [account] [/go]   ... HTML-dump files     ... option = 3 still to implement
  25.  
  26.            any other argument starts interactive mode   ... option = 0
  27.  
  28.  
  29. All rights reserved, copyrighted 1997, no guarantee that it works without
  30. errors, etc. etc.
  31.  
  32. donated to the public domain granted that you are not charging anything (money
  33. etc.) for it and derivates based upon it, as you did not write it,
  34. etc. if that holds you may bundle it with commercial programs too
  35.  
  36. you may freely distribute this program, granted that no changes are made
  37. to it
  38.  
  39. Please, if you find an error, post me a message describing it, I will
  40. try to fix and rerelease it to the net.
  41. */
  42.  
  43.  
  44. /* initialisation part of this Object Rexx program                              */
  45.  
  46.         /* PM-Mail 1.9 pre-defined values, store in .local environment          */
  47. .local ~ pmm.Deli  = "DE"x              /* field delimiter                      */
  48. .local ~ pmm.empty = "E1"x              /* empty field indicator                */
  49. .local ~ pmm.LF    = "E2"x              /* LF indicator                         */
  50. .local ~ pmm.CR    = "E3"x              /* CR indicator                         */
  51. .local ~ pmm.CRLF  = .pmm.cr || .pmm.lf
  52.  
  53.  
  54. .local ~ pmm.addrFile  = STREAM( "ADDR.DB",  "C", "QUERY EXISTS" )
  55. IF .pmm.addrFile = "" THEN
  56. DO
  57.    SAY "Error: pmmrxtoo.cmd must reside in South Side's Tools directory !"
  58.    SAY "       (did not find file" pp( "ADDR.DB" ) ||", aborting ... )"
  59.    SIGNAL ON SYNTAX                     /* set up catching                      */
  60.    RAISE SYNTAX 44.1 ARRAY ( "STREAM('ADDR.DB', 'C', 'QUERY EXISTS' )" )                         
  61. END
  62.  
  63. .local ~ pmm.assocFile = STREAM( "ASSOC.DB", "C", "QUERY EXISTS" )
  64. .local ~ pmm.booksFile = STREAM( "BOOKS.DB", "C", "QUERY EXISTS" )
  65. .local ~ pmm.groupFile = STREAM( "GROUP.DB", "C", "QUERY EXISTS" )
  66.  
  67. .local ~ pmm.drive    = FILESPEC( "D", .pmm.addrFile )
  68. tmpFile = STRIP( FILESPEC( "P", .pmm.addrFile ), "T", "\" )
  69. .local ~ pmm.toolPath = .pmm.drive || tmpFile
  70. .local ~ pmm.MailPath = SUBSTR( .pmm.toolPath, 1, LASTPOS( "\", .pmm.toolPath ) ) || "PMMAIL"
  71.  
  72.  
  73.  
  74.         /* set DB-files in class objects        */
  75. .addr     ~ filename = .pmm.addrFile
  76. .assoc    ~ filename = .pmm.assocFile
  77. .books    ~ filename = .pmm.booksFile
  78. .group    ~ filename = .pmm.groupFile
  79.  
  80.         /* globally used data           */
  81. tmpDBList = .list ~ of( .accounts, .addr, .assoc, .books, .group )
  82.         /* account related data         */
  83. tmpAccList = .list ~ of( .sigs, .creplies, .filters, .folder.ini )
  84.  
  85.         /* load DB-definitions, needed e.g. for lm_migr.cmd (LaMail -> PMM1.9 migration) */
  86. DO item OVER tmpDBList
  87.    item ~ newFromFile           /* create appropriate objects from file-definitions     */
  88. END
  89.  
  90.  
  91.  
  92. IF ARG() = 0 THEN RETURN        /* called via a REQUIRES directive or without arguments */
  93.  
  94.  
  95.         /* /s [account] [/go]   ... sort files          ... option = 1
  96.            /d [account] [/go]   ... dump files          ... option = 2
  97.            /h [account] [/go]   ... HTML-dump files     ... option = 3 still to implement
  98.  
  99.            any other argument starts interactive mode   ... option = 0
  100.         */
  101.  
  102.  
  103.  
  104. PARSE ARG "/"option account "/"go       /* parse command line           */
  105.  
  106. /*
  107. IF TRANSLATE( go ) <> "GO" THEN         /* warn user that PM-Mail *must* not be running !       */
  108. */
  109. DO
  110.    IF bisPMMailActive() THEN
  111.    DO
  112.       CALL Beep 2500, 250; CALL Beep 2500, 250; CALL Beep 2500, 250
  113.       SAY "PMMRXTOO.CMD: PM-Mail utility package (Object Rexx)"
  114.       SAY
  115.       SAY "ATTENTION !"
  116.       SAY
  117.       SAY "PM-Mail *MUST NOT* be running, otherwise unpredictable *ERRORs* occur !!"
  118.       SAY
  119.       SAY "Please close PM-Mail and restart this program."
  120.       EXIT -1
  121.    END
  122.  
  123. /*
  124.    answer = "Y"                         /* set default answer   */
  125.    answer  = get_choice( answer, "Is PM-Mail running ? (Y/n)" )
  126.    SAY
  127.    IF answer = "Y" THEN CALL abort
  128. */
  129. END
  130.  
  131. option = POS( TRANSLATE( LEFT( option, 1 ) ), "SDH" )   /* determine option     */
  132.  
  133. /* Bug in ORX-Parser !!!
  134.  
  135. option = POS( TRANSLATE( LEFT( option ) ), "SDH" )      /* determine option     */
  136. */
  137.  
  138.  
  139.  
  140. tmpAccObjColl = .accounts ~ ObjectColl       /* get account object collection        */
  141. bInteractive = .false           /* default to batch modus                       */
  142.  
  143. IF option = 0 THEN              /* interactive mode             */
  144. DO
  145.    bInteractive = .true         /* indicate that interactive modus is run       */
  146.    SAY "PMMRXTOO.CMD: PM-Mail utility package (Object Rexx)"
  147.    SAY
  148.    SAY "PM-Mail drive:         " pp( .pmm.drive )
  149.    SAY "PM-Mail toolPath:      " pp( .pmm.ToolPath )
  150.    SAY "PM-Mail MailPath:      " pp( .pmm.MailPath )
  151.    SAY
  152.    SAY "ADDR.BK         :      " pp( .pmm.addrFile )
  153.    SAY "ASSOC.BK        :      " pp( .pmm.assocFile )
  154.    SAY "BOOKS.BK        :      " pp( .pmm.booksFile )
  155.    SAY "GROUP.BK        :      " pp( .pmm.groupFile )
  156.    SAY
  157.    SAY
  158.  
  159.    SAY RIGHT( "(1)" , 10 ) "Sort all PM-Mail files"
  160.    SAY RIGHT( "(2)" , 10 ) "Dump content of all PM-Mail files"
  161. /*
  162.    SAY RIGHT( "(3)" , 10 ) "Dump content of all PM-Mail files in HTML"
  163. */
  164.    SAY
  165.    CALL CHAROUT , "Enter choice: 1-3 or 0, 'q' to quit: "
  166.  
  167.    option = get_answer( "Q", 3, "F" )
  168.    SAY; SAY
  169.    IF option = 0 | option = "Q" THEN call abort
  170.  
  171.  
  172.  
  173.    IF tmpAccObjColl ~ items = 1 THEN            /* only one account defined, use it     */
  174.    DO
  175.       tmpList = .list ~ of( tmpAccObjColl ~ firstitem )     /* retrieve account object      */
  176.       tmpDir = ( tmpAccObjColl ~ firstitem ) ~ objectData   /* get object data              */
  177.       SAY "using only account by default:" pp( tmpDir ~ acctdiskname ) pp( tmpDir ~ acctname ) 
  178.    END
  179.    ELSE
  180.    DO
  181.       answer = "A"                         /* set default answer   */
  182.       answer  = get_choice( answer, "Which account(s) do you wish to process ? (A/c)",,
  183.                             "Process A(ll) accounts or c(choose), q(uit)  ? (A/c)", "ACQ" )
  184.       bAllAcc = ( answer = "A" )
  185.    
  186.       IF \ bAllAcc THEN                         /* choose an account            */
  187.       DO
  188.          /* ask for all accounts of choosing single account   */
  189.          SAY "Choose the PM-Mail account to work with:"
  190.          SAY
  191.          i = 0
  192.          choice. = ""
  193.          DO item OVER .accounts ~ ObjectColl
  194.             i = i + 1
  195.             choice.i = item 
  196.             tmpDir = item ~ objectData 
  197.             SAY RIGHT( "(" || i || ")", 10 ) pp( tmpDir ~ acctdiskname ) pp( tmpDir ~ acctname ) 
  198.          END 
  199.          choice.0 = i                            /* save nr of items in stem             */
  200.          
  201.          SAY
  202.          CALL CHAROUT , "Enter choice: 1-" || i ", a(ll acounts), 0 or q(uit):"
  203.          answer = get_answer( "QA", i, "Force" )        /* ask user which account to use  */
  204.          IF answer = "Q" | answer = 0 THEN CALL abort
  205.          bAllAcc = ( answer = "A" )             /* all accounts or just a few ?         */
  206.  
  207.          IF \ bAllAcc THEN              /* save single account to process       */
  208.             tmpList = .list ~ of( choice.answer )       /* retrieve account object      */
  209.       END
  210.  
  211.       IF bAllAcc THEN                   /* work on all account objects  */
  212.          tmpList = tmpAccObjColl
  213.    END
  214. END
  215. ELSE                    /* batch mode: deal with arguments, see whether valid account was given */
  216. DO
  217.    IF account = "" THEN                 /* no account given, work on all accounts       */
  218.       tmpList = tmpAccObjColl
  219.    ELSE                                 /* look up account object               */
  220.    DO
  221.       account = TRANSLATE( STRIP( account ) )
  222.       tmpObj = .accounts ~ lookupTable ~ at( account )  /* retrieve account object      */
  223.  
  224.       IF tmpObj = .nil THEN 
  225.       DO
  226.          SAY "account" pp( account ) "does not exist, aborting ..."
  227.          CALL abort
  228.       END
  229.       tmpList = .list ~ of( tmpObj )    /* process specific account             */
  230.    END
  231. END
  232.  
  233.  
  234.  
  235.  
  236.         /* SORT-option                                                          */
  237. IF option = 1 THEN                      /* sort all files ...                   */
  238. DO
  239.         /* globally used data           */
  240.    DO tmpItem OVER tmpDBList            /* loop over DB-files                   */
  241.       tmpItem ~ sortedReplace           /* have class object sort its objects and replace the file */
  242.    END
  243.  
  244.         /* account related data         */
  245.    DO workAccObj  OVER tmpList          /* loop over account objects    */
  246.       tmpAcctPath = workAccObj ~ ObjectData ~ full_path 
  247.       DO tmpItem OVER tmpAccList        /* loop over collected objects  */
  248.          tmpItem ~ newFromFile( tmpAcctPath )
  249.          tmpItem ~ sortedReplace        /* have class object sort its objects and replace the file */
  250.       END 
  251.    END
  252. END
  253. ELSE
  254.  
  255.         /* DUMP-option                                                          */
  256. IF option = 2 THEN                      /* dump all relevant infos to STDOUT    */
  257. DO
  258.                 /* global data          */
  259.    outFile = "DUMP.TXT"
  260.    IF STREAM( outFile, "C", "QUERY EXISTS" ) <> "" THEN   /* exists already, chosse another name  */
  261.       outFile = SysTempFileName( "DUMP_???.TXT" )
  262.  
  263.    IF bInteractive THEN
  264.      SAY "dumping all chosen data to file" pp( outFile ) || ", be patient ... ;-)"
  265.  
  266.         /* set Object Rexx output monitor to the a stream (= file); 
  267.            i.e. all STDOUT-processing (e.g. SAY) will be directed to 
  268.            the file !!! (a real gimmick) */
  269.    tmpStream = .stream ~ new( outfile ) ~~ command( "OPEN WRITE" )
  270.  
  271.    .output ~ destination( tmpStream )     
  272.  
  273.    .output ~ SAY( "Dumping globally stored information ..." )
  274.    .output ~ SAY()
  275.    DO item OVER tmpDBList
  276.       item ~ dump                       /* dump all objects             */
  277.    END
  278.  
  279.                 /* account data         */
  280.    .output ~ SAY()
  281.    .output ~ SAY( "Dumping account - related information ..." )
  282.  
  283.    DO workAccObj  OVER tmpList          /* loop over account objects    */
  284.       .output ~ SAY( LEFT( "", 79, "=" ) )
  285.       .output ~ SAY()
  286.        workAccObj ~ dump                 /* dump account-infos           */
  287.       .output ~ SAY( LEFT( "", 60, "=" ) )
  288.  
  289.       tmpAcctPath = workAccObj ~ ObjectData ~ full_path
  290.       DO tmpItem OVER tmpAccList        /* loop over collected objects  */
  291.          tmpItem ~ newFromFile( tmpAcctPath )
  292.          tmpItem ~ dump                 /* have class dump its objects  */
  293.       END 
  294.    END
  295.  
  296.    .output ~ destination                /* restore previous Object Rexx output monitor  */
  297.    tmpStream ~ close                    /* close stream (= file)        */
  298.    SAY "Dumping finished, results in:" pp( outFile )
  299. END
  300.  
  301.  
  302. IF option = 3 THEN
  303. DO
  304.    IF bInteractive THEN
  305.      SAY "dumping all chosen data in HTML, be patient ... ;-)"
  306.  
  307.    CALL BEEP 2500, 250
  308.    SAY "HTML-dump not implemented yet, ..."
  309. END
  310.  
  311. EXIT
  312.  
  313. SYNTAX : RAISE PROPAGATE                /* raise error in caller        */
  314.  
  315.  
  316. get_choice : PROCEDURE                  /* general procedure to ask for an action       */
  317.    USE ARG answer, prompt, choice_prompt, choose_string
  318.  
  319.    IF \ VAR( "choice_prompt" ) THEN choice_prompt = "Choices: y(es), n(o), q(uit)"
  320.    IF \ VAR( "choose_string" ) THEN choose_string = "YNQ"
  321.  
  322.    SAY; SAY; SAY
  323.    SAY LEFT( prompt || " ", 79, "=" )
  324.    SAY
  325.    SAY choice_prompt 
  326.    SAY 
  327.    CALL CHAROUT , "Enter your choice: "
  328.    tmpAnswer = get_answer( choose_string )
  329.  
  330.    IF POS( tmpAnswer, choose_string) = 0 THEN           /* use default  */
  331.    DO
  332.       tmpAnswer = answer
  333.       SAY answer "(default)"
  334.    END
  335.  
  336.    IF tmpAnswer = "Q" THEN CALL abort                   /* abort        */
  337.  
  338.    RETURN tmpAnswer
  339.  
  340. abort : PROCEDURE 
  341.    CALL BEEP 2000, 250
  342.    SAY "aborting PM-Mail tool 'PMMRXTOO.CMD' (Object Rexx) ..."
  343.    EXIT -1
  344.  
  345.  
  346. :: REQUIRES rgf_util                    /* needs some of RGF_UTIL's functions   */
  347. :: REQUIRES get_answ                    /* routine get_answer defined   */
  348.  
  349.  
  350. :: ROUTINE PP                           /* cheap pretty-print routine           */
  351.    RETURN "[" || ARG( 1 ) || "]"
  352.                                                                                               
  353.  
  354.  
  355.  
  356.  
  357.  
  358.  
  359.  
  360.  
  361. /* ============================================== class definition ==================== */
  362.         /* abstract class, serves for subclassing       */
  363. :: CLASS "PM_Mail"                              
  364.  
  365.         /* -------------------------------------- method definition (class) ----------- */
  366. :: METHOD INIT                  CLASS
  367.  
  368.    self ~ ObjectColl = .list ~ new
  369.    self ~ MaxNumber  = 0
  370.    self ~ FileName   = ""
  371.    self ~ LeadInData = ""
  372.  
  373.  
  374. :: METHOD DropObjects           CLASS   /* drop all objects, reset to initial state     */
  375.    self ~ init                          /* reinitialize                         */
  376.  
  377.  
  378. :: METHOD setLongestProperty    CLASS
  379.    max = 0                              /* determine length of longest property name    */
  380.  
  381.    DO item OVER self ~ PropertyList
  382.       max = MAX( max, LENGTH( item ) )
  383.    END
  384.    self ~ LongestProperty = max
  385.  
  386.  
  387. :: METHOD FileName              CLASS ATTRIBUTE         /* default file name for building objects*/
  388. :: METHOD HeadingTag            CLASS ATTRIBUTE         /* porperty name to be used as heading  */
  389. :: METHOD LongestProperty       CLASS ATTRIBUTE         /* no. chars of longest property name   */
  390. :: METHOD MaxNumber             CLASS ATTRIBUTE         /* maximum surrogate number used        */
  391. :: METHOD ObjectColl            CLASS ATTRIBUTE         /* collection of instantiated objects   */
  392. :: METHOD PropertyList          CLASS ATTRIBUTE         /* list of stored properties            */
  393.  
  394. :: METHOD leadinData            CLASS ATTRIBUTE         /* store version number if in file      */
  395.  
  396.  
  397. :: METHOD sortedReplace         CLASS           /* sort objects and replace file                */
  398.  
  399.    IF self ~ ObjectColl ~ items = 0 THEN RETURN /* nothing to do                */
  400.    workFile = self ~ FileName
  401.  
  402.         /* rgf_util sort, define collection, method to send it, arguments to go with method     */
  403.    tmpArray = sortCollection( self ~ ObjectColl, .array ~ of( "SORT", .array ~ of( self ~ HeadingTag ) )  ) 
  404.  
  405.  
  406.  
  407.    CALL STREAM workFile, "C", "OPEN REPLACE"    /* open file for replacement    */
  408.  
  409.    IF self ~ leadinData <> "" THEN              /* a leadin (PM-Mail version number ?   */
  410.       CALL LINEOUT workFile, self ~ leadinData
  411.  
  412.    DO i = 1 TO tmpArray ~ items / 2
  413.       CALL LINEOUT workFile, tmpArray[ i, 2 ]   /* MAKESTRING will turn objects into PMMail-format */
  414.    END
  415.  
  416.    CALL STREAM workFile, "C", "CLOSE"           /* close                        */
  417.  
  418.  
  419.  
  420.  
  421.         /* use .output environment monitor; it'll print to STDOUT by default, 
  422.            but it is possible to change its destination temporarily, e.g. to a file     */
  423. :: METHOD dump                  CLASS           /* dump entire collection               */
  424.  
  425.    .output ~ SAY( "dumping all objects of type" pp( self ~ id ) )
  426.    tmpString = "                   total of" pp( self ~ ObjectColl ~ items ) "objects"
  427.    items = self ~ ObjectColl ~ items
  428.    IF items > 0 THEN tmpString = tmpString || ", highest used number" pp( self ~ MaxNumber )
  429.    .output ~ SAY( tmpString )
  430.        
  431.  
  432.    IF self ~ hasMethod( "FileName" ) THEN       /* is property method "FileName" there? */
  433.       .output ~ SAY( "               created from" pp( self ~ FileName ) )
  434.  
  435.    .output ~ SAY()
  436.  
  437.         /* rgf_util sort, define collection, method to send it, arguments to go with method     */
  438.    tmpArray = sortCollection( self ~ ObjectColl, .array ~ of( "SORT", .array ~ of( self ~ HeadingTag ) )  ) 
  439.  
  440.    DO i = 1 TO tmpArray ~ items / 2
  441.       tmpArray[ i, 2 ] ~ dump
  442.    END
  443.    .output ~ SAY( LEFT( "", 60, "=" ) )
  444.  
  445.  
  446. :: METHOD NewFromString         CLASS   /* create instance from string          */
  447.    USE ARG allString
  448.  
  449.    crlf = "0D0A"x                       /* records delimited by CR-LF           */
  450.  
  451.    DO WHILE allString <> ""
  452.       PARSE VAR allString tmpString  ( crlf ) allString
  453.  
  454.       tmpDir = .directory ~ new
  455.       DO property OVER self ~ PropertyList 
  456.          PARSE VAR tmpString tmpValue ( .pmm.Deli ) tmpString
  457.    
  458.          IF tmpValue <> .pmm.empty THEN /* only store non-empty values          */
  459.             tmpDir ~ setentry( property, TRANSLATE( tmpValue, crlf , .pmm.crlf  ) )
  460.    
  461.          IF tmpString = "" THEN LEAVE   /* string exhausted, if so leave        */
  462.       END                                       
  463.                                                 
  464.       IF tmpString <> "" THEN           /* save leftovers  untouched            */
  465.          tmpDir ~ xleftover = tmpString
  466.    
  467.       tmpObj = self ~ new( tmpDir )
  468.    END
  469.  
  470.    IF VAR( "tmpObj" ) THEN RETURN tmpObj        /* return last instance created */
  471.  
  472.  
  473.  
  474.  
  475. :: METHOD newFromFile        CLASS      /* read file and create instances off of it     */
  476.    USE ARG inFile
  477.  
  478.    IF \ VAR( "inFile" ) THEN                    /* no argument given ?          */
  479.      inFile = self ~ FileName                   /* use file stored with class   */
  480.  
  481.    CALL STREAM inFile, "C", "OPEN READ"         /* read entire file             */
  482.  
  483.    tmpLine = LINEIN( inFile )                   /* read first line              */
  484.    IF DATATYPE( tmpLine, "N" ) THEN
  485.    DO
  486.       self ~ leadinData = tmpLine               /* save leadin                  */
  487.       tmpString = CHARIN( inFile,  , CHARS( inFile ) )  /* read remaining chars */
  488.    END
  489.    ELSE
  490.    DO
  491.       CALL CHARIN inFile, 1, 0  /* set read position to the beginning for CHARS() */
  492.       tmpString = CHARIN( inFile, 1, CHARS( inFile ) )  /* read entire file     */
  493.    END
  494.  
  495.    CALL STREAM inFile, "C", "CLOSE"
  496.  
  497.    self ~ NewFromString( tmpString )            /* create objects from file     */
  498.  
  499.  
  500.  
  501.  
  502.  
  503.  
  504.  
  505.  
  506.         /* -------------------------------------- method definition (instance) -------- */
  507. :: METHOD INIT                          /* initalisation, storing directory for object  */
  508.    USE ARG tmpDir 
  509.  
  510.    self ~ ObjectData = tmpDir
  511.  
  512.    self ~ class ~ ObjectColl   ~ insert( self ) /* add to list stored with class        */
  513.  
  514.    IF tmpDir ~ hasentry( "NUMBER" ) THEN        /* determine maximum number             */
  515.       self ~ class ~ MaxNumber = MAX( self ~ class ~ MaxNumber, tmpDir ~ number )
  516.  
  517.  
  518. :: METHOD ObjectData   ATTRIBUTE        /* allow access to nickName             */
  519.  
  520.         
  521.  
  522.         /* use .output environment monitor; it'll print to STDOUT by default, 
  523.            but it is possible to change its destination temporarily, e.g. to a file     */
  524. :: METHOD dump                          /* dump contents of object              */
  525.    objectDir   = self ~ ObjectData
  526.    HeadingTag  = self ~ class ~ HeadingTag
  527.    maxLength   = self ~ class ~ LongestProperty
  528.  
  529.    .output ~ SAY( HeadingTag pp( objectDir ~ entry( HeadingTag ) ) )
  530.    HeadingTag = TRANSLATE( HeadingTag )
  531.  
  532.  
  533.    tmpArray = sortCollection( objectDir )
  534.    DO i = 1 TO objectDir ~ items
  535.       IF tmpArray[ i, 1 ] = HeadingTag THEN ITERATE
  536.  
  537.       .output ~ SAY( "   " LEFT( tmpArray[ i, 1 ], maxLength ) pp( tmpArray[ i, 2 ] ) )
  538.    END
  539.    SAY
  540.    RETURN
  541.  
  542.  
  543.  
  544. :: METHOD MakeString                    /* render object into string representation     */
  545.    tmpString = ""
  546.    tmpDir = self ~ ObjectData
  547.  
  548.    crlf = "0d0a"x
  549.  
  550.    DO property OVER self ~ class ~ PropertyList
  551.  
  552.       tmpProp = tmpDir ~ entry( property )      /* get property                 */
  553.  
  554.  
  555.       IF tmpProp = .nil | tmpProp = "" THEN
  556.          tmpString = tmpString || .pmm.empty || .pmm.Deli
  557.       ELSE
  558.          tmpString = tmpString || TRANSLATE( tmpProp, .pmm.crlf, crlf ) || .pmm.Deli
  559.    END
  560.  
  561.    IF tmpDir ~ hasentry( "XLEFTOVER" ) THEN     /* trailing fields, if so append        */
  562.       tmpString = tmpString || tmpDir ~ xLeftOver
  563.  
  564.    RETURN tmpString
  565.  
  566.  
  567. :: METHOD unknown                       /* unknown message was received         */
  568.    USE ARG msgName, msgArgArray
  569.  
  570.    SELECT
  571.       WHEN msgName = "SORTFIELD" THEN   /* return entry stored with HeadingTag in ObjectData */
  572.            RETURN ( self ~ ObjectData ~ entry( self ~ class ~ HeadingTag ) )
  573.  
  574.       WHEN msgName = "SORT"      THEN   /* return entry of ObjectData, according to 1st msg-argument */
  575.            RETURN ( self ~ ObjectData ~ entry( msgArgArray[ 1 ] ) )
  576.  
  577.       OTHERWISE 
  578.            DO
  579.               SIGNAL ON SYNTAX          /* set up catching                      */
  580.               RAISE SYNTAX 97.1 ARRAY ( self, msgName ) /* raise appropriate error      */              
  581.            END
  582.    END  
  583.    RETURN
  584.  
  585. SYNTAX : RAISE PROPAGATE                /* raise syntax error in caller's position      */
  586.  
  587.  
  588.  
  589.  
  590. /* ============================================== class definition ==================== */
  591.         /* class to handle PM-Mail files in more detail */
  592. :: CLASS pmmail_DB      SUBCLASS "PM_Mail"      /* load definitions from DB-file        */
  593.  
  594.  
  595.         /* -------------------------------------- method definition (class) ----------- */
  596. :: METHOD INIT                  CLASS
  597.    FORWARD CLASS ( super ) CONTINUE             /* let super initialize                 */
  598.    self ~ LookupTable = .table ~ new            /* initialize LookupTable               */
  599.  
  600.  
  601. :: METHOD DropObjects           CLASS           /* reset class variables                */
  602.  
  603.    self ~ LookupTable = .table ~ new
  604.    FORWARD CLASS( super )
  605.  
  606.  
  607.  
  608. :: METHOD LookupTable           CLASS   ATTRIBUTE       
  609.  
  610. :: METHOD newFromFile        CLASS              /* check whether already created        */
  611.  
  612.    IF self ~ ObjectColl ~ items > 0 THEN        /* already created ?                    */
  613.    DO
  614.       SAY pp( self ~ id || "::newFromFile::Class" ) "file" pp( self ~ fileName ),
  615.           "already used, aborting (no new objects created) ..."
  616.       RETURN
  617.    END
  618.  
  619.    FORWARD CLASS ( super )              /* let super do the actual work         */
  620.  
  621.  
  622.  
  623.         /* -------------------------------------- method definition (instance) -------- */
  624. :: METHOD INIT                  /* build LookupTable            */
  625.  
  626.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize first           */
  627.    selfClass = self ~ class             /* get self's class object              */
  628.                 /* "LookupTable[ HeadingTag ] = self", use "HeadingTag" as lookup-field:*/
  629.    selfClass ~ LookupTable ~ put( self, self ~ ObjectData ~ entry( selfClass ~ HeadingTag ) )
  630.  
  631.  
  632.  
  633.  
  634.  
  635. /* ============================================== class definition ==================== */
  636.          /* class for addresses */
  637. :: CLASS "Addr"                 SUBCLASS pmmail_DB PUBLIC
  638.  
  639.         /* -------------------------------------- method definition (class) ----------- */
  640. :: METHOD INIT                  CLASS
  641.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  642.    self ~ FileName = .pmm.addrFile      /* set filename                         */
  643.  
  644.         /* pattern for parsing                                  */
  645.    self ~ PropertyList = .list ~ of( "E_MAIL", "ALIAS", "TRUENAME", "SHOW_ON_RMB", "COMPANY", "TITLE",,
  646.        "H_STREET", "H_BLDG", "H_CITY", "H_STATE", "H_ZIP", "H_PHONE", "H_EXT", "H_FAX",,
  647.        "B_STREET", "B_BLDG", "B_CITY", "B_STATE", "B_ZIP", "B_PHONE", "B_EXT", "B_FAX",,
  648.        "NOTES", "IN_BOOK_NR",,
  649.        "H_COUNTRY", "B_COUNTRY" )
  650.  
  651.    self ~ setLongestProperty
  652.  
  653.    self ~ HeadingTag = "ALIAS"          /* Tag to be shown                      */
  654.  
  655.  
  656.  
  657.  
  658.  
  659.  
  660. /* ============================================== class definition ==================== */
  661.          /* class for addresses */
  662. :: CLASS "Assoc"                SUBCLASS pmmail_DB      PUBLIC
  663.  
  664.         /* -------------------------------------- method definition (class) ----------- */
  665. :: METHOD INIT                  CLASS
  666.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  667.    self ~ FileName = .pmm.assocFile     /* set filename                         */
  668.  
  669.         /* pattern for parsing                                  */
  670.    self ~ PropertyList = .list ~ of( "TITLE", "MIME_TYPE", "MIME_EXT",,
  671.                                       "PROGRAM", "ARGS", "WORKING_DIR", "SESSIONTYPE" )
  672.    self ~ setLongestProperty
  673.  
  674.    self ~ HeadingTag = "TITLE"          /* Tag to be shown                      */
  675.  
  676.  
  677.  
  678.  
  679. /* ============================================== class definition ==================== */
  680.          /* class for books     */
  681. :: CLASS "Books"                SUBCLASS pmmail_DB      PUBLIC
  682.  
  683.         /* -------------------------------------- method definition (class) ----------- */
  684. :: METHOD INIT                  CLASS
  685.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  686.    self ~ FileName = .pmm.booksFile     /* set filename                         */
  687.  
  688.         /* pattern for parsing                                  */
  689.    self ~ PropertyList = .list ~ of( "NAME", "SORT_DESCE", "SORT_FIELD_NR", "NUMBER" )
  690.    self ~ setLongestProperty
  691.  
  692.    self ~ HeadingTag = "NAME"           /* Tag to be shown                      */
  693.         /* sort fields: 0 ... Alias
  694.                         1 ... Real Name
  695.                         2 ... E-Mail
  696.                         3 ... Phone Number
  697.         */
  698.  
  699.  
  700.  
  701.  
  702.  
  703.  
  704. /* ============================================== class definition ==================== */
  705.          /* class for groups    */
  706. :: CLASS "Group"                SUBCLASS pmmail_DB      PUBLIC
  707.  
  708.         /* -------------------------------------- method definition (class) ----------- */
  709. :: METHOD INIT                  CLASS
  710.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  711.    self ~ FileName = .pmm.groupFile     /* set filename                         */
  712.  
  713.         /* pattern for parsing                                  */
  714.    self ~ PropertyList = .list ~ of( "SHOW_ON_RMB", "NAME", "ALIAS", "DESCRIPTION",,
  715.                                       "PATH", "IN_BOOK_NR" )
  716.    self ~ setLongestProperty
  717.  
  718.    self ~ HeadingTag = "ALIAS"          /* Tag to be shown                      */
  719.  
  720.  
  721.  
  722.  
  723.  
  724.  
  725.         /* ******************************************** */
  726.         /* account related classes ....                 */
  727.         /* ******************************************** */
  728.  
  729. /* ============================================== class definition ==================== */
  730.          /* class for *all* accounts    */
  731. :: CLASS "Accounts"             SUBCLASS pmmail_db      PUBLIC
  732.  
  733.         /* -------------------------------------- method definition (class) ----------- */
  734. :: METHOD INIT                  CLASS
  735.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  736.  
  737.         /* pattern for parsing                                  */
  738.    self ~ PropertyList = .list ~ of( "ACCTNAME", "ACCTDISKNAME", "FROMEMAIL", "FROMREAL",, 
  739.                          "POPRECVSERV", "POPSENDSERV", "SMTPPATH", "SMTPSENDSERV", "EDITOR",,
  740. /* MSG-Send Exit     */  "REXXSEND",    "SENDEXIT",     "SENDBG",,
  741. /* MSG-Receive Exit  */  "REXXRECV",    "RECVEXIT",     "RECVBG",,
  742. /* Cust-REXX-Send    */  "REXXCUSTS",   "CUSTS",        "CUSTSBG",,
  743. /* Cust-REXX-Fetch   */  "REXXCUSTF",   "CUSTF",        "CUSTFBG",,
  744. /* Dialer Start Exit */  "REXXBEGD",    "BEGDIAL",      "BEGDIALBG",,
  745. /* Dialer End Exit   */  "REXXENDD",    "ENDDIAL",      "ENDDIALBG" )  
  746.                       /* Rexx-file      Activated ?     Background execution ?  */
  747.                                      
  748.    self ~ setLongestProperty
  749.    self ~ HeadingTag = "ACCTNAME"       /* Tag to be shown                      */
  750.  
  751.  
  752.  
  753. :: METHOD newFromFile        CLASS
  754.  
  755.    self ~ dropObjects                   /* make sure to start on a clean sheet  */
  756.    filePattern = .pmm.MailPath || "\*.act"
  757.    self ~ filename = filePattern
  758.  
  759.  
  760.    CALL SysFileTree  filePattern, "files.", "DO"
  761.  
  762.  
  763.    lookupTable = self ~ LookupTable             /* get lookup table             */
  764.    DO i = 1 TO files.0                  /* loop over individual accounts        */
  765.       tmpObj = self ~ new( parse_file( self, files.i ) )        /* create an instance   */
  766.                 /* add an additional lookup entry (allowing lookups on physical name of acctdir */
  767.       lookupTable ~ put( tmpObj, TRANSLATE( tmpObj ~ objectdata ~ acctdiskname ) )
  768.    END
  769.    RETURN
  770.  
  771. parse_file : PROCEDURE
  772.    USE ARG classObj, path
  773.  
  774.    file  = path || "\acct.cfg"
  775.    fileInfo = CHARIN( file, 1, CHARS( file ) )  /* read entire file             */
  776.    CALL STREAM file, "C", "CLOSE"
  777.  
  778.  
  779.    tmpDir = .directory ~ new                   
  780.    tmpDir ~ setentry( "full_path", path )       /* save path to ACCT-file               */
  781.    x00 = "00"x                                  
  782.  
  783.    DO property OVER classObj ~ PropertyList         /* extract desired infos from account   */
  784.       tmpProperty = x00 || property || x00
  785.       PARSE VAR fileInfo ( tmpProperty ) value (x00) .
  786.  
  787.       tmpDir ~ setentry( property, value )
  788.    END
  789.  
  790.    CALL deal_with_rexx_exits classObj, tmpDir   /* deal with REXX-exits (make them readable) */
  791.  
  792.         /* now it is safe to remove empty entries       */
  793.    DO index OVER tmpDir
  794.       IF tmpDir ~ entry( index ) = "" THEN      /* remove empty entries */
  795.          tmpDir ~ setentry( index )
  796.    END
  797.  
  798.  
  799.    RETURN tmpDir
  800.  
  801.  
  802.         /* render Rexx-exits into a more readable form  */
  803. Deal_with_rexx_exits : procedure                     /* check for exits, replace entries in Dir      */
  804.    USE ARG classObj, tmpDir
  805.  
  806.  
  807.     specArr = .array ~ of( ,                            /* replacement array            */
  808. /* --> */ "REXXSEND",  '_Rx10_MSG_Send_Exit',,
  809.           "SENDEXIT",  '_Rx11_MSG_Send_Active',,
  810.           "SENDBG",    '_Rx11_MSG_Send_exec_FG',,
  811. /* --> */ "REXXRECV",  '_Rx20_MSG_Rec_Exit'     ,,  
  812.           "RECVEXIT",  '_Rx21_MSG_Rec_Active'         ,,
  813.           "RECVBG",    '_Rx21_MSG_Rec_exec_FG',,
  814. /* --> */ "REXXCUSTS", '_Rx30_Cst_RX_Send'   ,,  
  815.           "CUSTS",     '_Rx31_Cst_RX_Send_Active'  ,,    
  816.           "CUSTSBG",   '_Rx31_Cst_RX_Send_exec_FG',,
  817. /* --> */ "REXXCUSTF", '_Rx40_Cst_RX_Fetch'  ,,  
  818.           "CUSTF",     '_Rx41_Cst_RX_Fetch_Active' ,,    
  819.           "CUSTFBG",   '_Rx41_Cst_RX_Fetch_exec_FG',,
  820. /* --> */ "REXXBEGD",  '_Rx50_Dial_Start_Exit',,  
  821.           "BEGDIAL",   '_Rx51_Dial_Start_Active'    ,,    
  822.           "BEGDIALBG", '_Rx51_Dial_Start_exec_FG',,
  823. /* --> */ "REXXENDD",  '_Rx60_Dial_End_Exit'  ,,  
  824.           "ENDDIAL",   '_Rx61_Dial_End_Active'      ,,    
  825.           "ENDDIALBG", '_Rx61_Dial_End_exec_FG'    )
  826.                                                                                                                   
  827.  
  828.    max = classObj ~ LongestProperty             /* get maximum number   */
  829.    DO i = 2 TO specArr ~ items BY 2
  830.       max = MAX( max, LENGTH( specArr[ i ] ) )
  831.    END
  832.    classObj ~ LongestProperty = max             /* set maximum number   */
  833.  
  834.  
  835.    items = 6                                    /* items to handle per rexx-exit  */
  836.    nrElements = specArr ~ items 
  837.    increment = nrElements / items
  838.    DO i = 1 TO nrElements BY increment
  839.       IF tmpDir ~ entry( specArr[ i ] ) <> "" THEN
  840.       DO
  841.          value = tmpDir ~ entry( specArr[ i ] )         /* get value    */
  842.          tmpDir ~ setentry( specArr[ i ] )              /* remove entry */
  843.          tmpDir ~ setentry( specArr[ i + 1 ], value )   /* store under new name */
  844.    
  845.          bIsBG = .false                           /* second is background */
  846.          DO k = i + 2 TO nrElements FOR 2 BY 2       /* deal with flags      */
  847.             value = tmpDir ~ entry( specArr[ k ] )              /* get value    */
  848.  
  849.             tmpDir ~ setentry( specArr[ k ] )                   /* remove entry */
  850.             IF bIsBG THEN
  851.                tmpDir ~ setentry( specArr[ k + 1 ], (\C2D( value )) ) /* store under new name */
  852.             ELSE
  853.                tmpDir ~ setentry( specArr[ k + 1 ], C2D( value ) ) /* store under new name */
  854.             bIsBg = \ bIsBg                         /* switch logical value */
  855.          END
  856.       END
  857.       ELSE
  858.       DO
  859.                 /* no REXX-file in Rexx-exit            */
  860.          DO k = i TO nrElements FOR 3 BY 2              /* delete this set of entries   */
  861.             tmpDir ~ setentry( specArr[ k ] )
  862.          END
  863.       END
  864.    END
  865.    RETURN
  866.  
  867.  
  868.  
  869.  
  870.  
  871.  
  872.  
  873. :: METHOD sortedReplace         CLASS   /* intercept, not allowed to write directly to ACCT.CFG ! */
  874.  
  875.  
  876.  
  877.  
  878. /* ============================================== class definition ==================== */
  879.          /* class for signatures                */
  880. :: CLASS "PM_Mail_Account"      SUBCLASS "PM_Mail"      PUBLIC
  881.  
  882.         /* -------------------------------------- method definition (class) ----------- */
  883. :: METHOD DeterminePath         CLASS   /* determine path for newFromFile       */
  884.    USE ARG PATH
  885.  
  886.    IF \ VAR( "PATH" ) | ARG( 1 ) = "PATH" THEN
  887.       path = .pmm.MailPath      /* default to FOLDER.INIs of all accounts       */
  888.    ELSE 
  889.       IF IsA( path, .accounts ) THEN            /* an account object ?          */
  890.          path = path ~ ObjectData ~ full_path   /* replace with path of account */          
  891.  
  892.    RETURN path
  893.  
  894.  
  895.  
  896.  
  897.  
  898. /* ============================================== class definition ==================== */
  899.          /* class for mail.ini's        */
  900. :: CLASS "Folder.Ini"           SUBCLASS "PM_Mail_Account"      PUBLIC
  901.  
  902.         /* -------------------------------------- method definition (class) ----------- */
  903. :: METHOD INIT                  CLASS
  904.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  905.  
  906.         /* pattern for parsing                                  */
  907.    self ~ PropertyList = .list ~ of( "NAME", "NUMBER", "USER_DEFINED", "SORT_FIELDS",,
  908.                                      "SORT_DESCE", "INDICATE_UNREAD_MAIL", "INDICATE_HAS_MAIL",,
  909.                                      "FOLDER_ICON" )
  910. /* FOLDER_ICON values:                                     
  911.  
  912.                    Folder   DownArrow   Color-meaning
  913.         yellow       0          3       don't indicate any mail or (all mail read and not any mail indicator)
  914.         red          1          4       indicate, if any mail, but all mail read
  915.         green        2          5       indicate, if any unread mail
  916.  
  917. Hint: super-folders will turn to an arrow, if subfolders hava triggering mail indicators
  918.       super-folders color will tell the mail-state of that super-folder
  919.       super-folders folder value (=its state) will be added to DownArrow, if necessary
  920. */
  921.  
  922.  
  923.    self ~ setLongestProperty
  924.    self ~ HeadingTag = "NAME"           /* Tag to be shown                      */
  925.  
  926.  
  927. :: METHOD sortedReplace         CLASS   /* intercept this method, doesn't apply to folders ! */
  928.  
  929.  
  930. :: METHOD newFromFile        CLASS
  931.    USE ARG path
  932.  
  933.    path = self ~ determinePath( path )  /* set path for this instance           */
  934.    self ~ DropObjects
  935.    self ~ fileName = path
  936.  
  937.    filePattern = path || "\FOLDER.INI"
  938.    CALL SysFileTree  filePattern, "files.", "FOS"
  939.  
  940.    bInboxFound = .false
  941.    DO i = 1 TO files.0                  /* loop over individual accounts        */
  942.       tmpString = LINEIN( files.i )     /* read content of file == 1 line       */
  943.  
  944.       IF DATATYPE( tmpString, "N" ) THEN        /* a PM-Mail version line in hand ?     */
  945.          tmpString = LINEIN( files.i )          /* if so, read second line              */
  946.  
  947.       CALL STREAM files.i, "C", "CLOSE"
  948.  
  949.       tmpObj = self ~ NewFromString( tmpString )   /* create instance from String          */
  950.       tmpObj ~ ObjectData ~ Xfull_path = files.i   /* save full path for file              */
  951.  
  952.       IF \ bInboxFound THEN             /* look for PM-Mail's "Inbox" object */
  953.       DO
  954.          IF tmpObj ~ ObjectData ~ number = 1 THEN 
  955.          DO
  956.             inboxObj = tmpObj
  957.             bInboxFound = .true
  958.          END
  959.       END
  960.    END
  961.    RETURN inboxObj
  962.  
  963.  
  964. /* ============================================== class definition ==================== */
  965.          /* class for signatures                */
  966. :: CLASS "Sigs"                 SUBCLASS "PM_Mail_Account"      PUBLIC
  967.  
  968.         /* -------------------------------------- method definition (class) ----------- */
  969. :: METHOD INIT                  CLASS
  970.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  971.  
  972.         /* pattern for parsing                                  */
  973.    self ~ PropertyList = .list ~ of( "NAME", "PATH_TO_FILE", "IS_DEFAULT" )
  974.                                      
  975.    self ~ setLongestProperty
  976.  
  977.    self ~ HeadingTag = "NAME"           /* Tag to be shown                      */
  978.           
  979.  
  980.  
  981. :: METHOD newFromFile        CLASS
  982.    USE ARG path
  983.  
  984.    path = self ~ determinePath( path )  /* set path for this instance           */
  985.    self ~ DropObjects
  986.  
  987.    inFile = path || "\SIGS\SIGS.LST"  
  988.    self ~ fileName = inFile 
  989.    self ~ newFromFile : super( inFile ) /* use PM_MAIL's newFromFile */
  990.  
  991.  
  992.  
  993.  
  994. /* ============================================== class definition ==================== */
  995.          /* class for canned replies, could be subclassed from "SIGS", but left it for clearity */
  996. :: CLASS "Creplies"             SUBCLASS "PM_Mail_Account"      PUBLIC
  997.  
  998.         /* -------------------------------------- method definition (class) ----------- */
  999. :: METHOD INIT                  CLASS
  1000.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  1001.  
  1002.         /* pattern for parsing                                  */
  1003.    self ~ PropertyList = .list ~ of( "NAME", "PATH_TO_FILE" )
  1004.                                      
  1005.    self ~ setLongestProperty
  1006.  
  1007.    self ~ HeadingTag = "NAME"           /* Tag to be shown                      */
  1008.  
  1009.  
  1010.  
  1011. :: METHOD newFromFile        CLASS
  1012.    USE ARG path
  1013.  
  1014.    path = self ~ determinePath( path )  /* set path for this instance           */
  1015.    self ~ DropObjects
  1016.  
  1017.    inFile = path || "\CREPLIES\CREPLIES.LST"
  1018.    self ~ fileName = inFile 
  1019.  
  1020.    self ~ newFromFile : super( inFile ) /* use PM_MAIL's newFromFile */
  1021.  
  1022.  
  1023.  
  1024.  
  1025.  
  1026.  
  1027.  
  1028. /* ============================================== class definition ==================== */
  1029.          /* class for canned replies, could be subclassed from "SIGS", but left it for clearity */
  1030. :: CLASS "Filters"              SUBCLASS "PM_Mail_Account"      PUBLIC
  1031.  
  1032. :: METHOD sortedReplace         CLASS   /* intercept this method, must not apply to filters,
  1033.                                            as order of filters maybe significant        */
  1034.  
  1035.         /* -------------------------------------- method definition (class) ----------- */
  1036. :: METHOD INIT                  CLASS
  1037.    FORWARD CLASS ( super ) CONTINUE     /* let super initialize                 */
  1038.  
  1039.         /* pattern for parsing                                  */
  1040.    self ~ PropertyList = .list ~ of( "NAME", "IS_ENABLED", "IS_COMPLEX", "CONNECT_TYPE",,
  1041.                                      "TRIGGERED_WHEN", "SEARCH1", "SEARCH1_FOR",,
  1042.                                      "SEARCH2", "SEARCH2_FOR", "COMPL_STRING",,
  1043.                                      "ACTION1", "ACTION2", "ACTION3", "ACTION4",,
  1044.                                      "ACTION5", "ACTION6" )
  1045.  
  1046.         /* 
  1047.            SEARCH :             "free text"
  1048.  
  1049.  
  1050.  
  1051.             CONNECT_TYPE :      0 ... not connected
  1052.                                 1 ... AND
  1053.                                 2 ... OR
  1054.                                 3 ... UNLESS
  1055.  
  1056.             TRIGERRED_WHEN :    add values:
  1057.  
  1058.                                 1 ... INCOMING
  1059.                                 2 ... OUTGOING (pre-send)
  1060.                                 4 ... OUTGOING (post-send)
  1061.                                 8 ... MANUAL
  1062.  
  1063.             ACTION
  1064.  
  1065.  
  1066.         */
  1067.                                      
  1068.    self ~ setLongestProperty
  1069.    self ~ HeadingTag = "NAME"           /* Tag to be shown                      */
  1070.  
  1071.  
  1072. :: METHOD newFromFile           CLASS
  1073.    USE ARG path
  1074.  
  1075.    path = self ~ determinePath( path )  /* set path for this instance           */
  1076.    self ~ DropObjects
  1077.  
  1078.    inFile = Path || "\FILTERS.LST"      /* build file-name              */
  1079.    self ~ fileName = inFile 
  1080.  
  1081.    self ~ newFromFile : super( inFile ) /* use PM_MAIL's newFromFile */
  1082.  
  1083.  
  1084.  
  1085.  
  1086. /* check whether PMMail is running, i.e. whether "PMMail" is found in the task-list;
  1087.    this routine uses the *undocumented* RexxUtil-function "SysQuerySwitchList()";            
  1088.    returns .true, if PMMail runs, .false if it is not running   */
  1089. :: ROUTINE bisPMMailActive      PUBLIC        
  1090.    
  1091.    needle = "PMMAIL"                    /* ignore case          */
  1092.    CALL SysQuerySwitchList  "tasks." 
  1093.    
  1094.    DO i = 1 TO tasks.0; 
  1095.       IF POS( needle, TRANSLATE( tasks.i ) ) > 0 THEN RETURN .true
  1096.    END
  1097.    RETURN .false
  1098.  
  1099.  
  1100.  
  1101.  
  1102.