cm3ide/src/nodes/FileNode.m3


 Copyright 1996 Critical Mass, Inc. All rights reserved.    

MODULE FileNode;

IMPORT Pathname, Rd, Thread, Wr;
IMPORT ConfigItem, ErrLog, HTML, ID, Node, OS, Text2, Wx;

TYPE
  FileKind = [0..LAST(FileMap)];
  ExtMap   = RECORD extension, file_type: TEXT; END;

CONST
  FirstHTML = 1;  (* index into FileMap *)
  LastHTML  = 2;

CONST
  FileMap = ARRAY OF ExtMap {
    ExtMap { "???",     "text/plain" },  (* initial kind = 0 *)
    ExtMap { "html",    "text/html" },
    ExtMap { "htm",     "text/html" },
    ExtMap { "gif",     "image/gif" },
    ExtMap { "ps",      "application/postscript" },
    ExtMap { "pdf",     "application/pdf" },
    ExtMap { "text",    "text/plain" },
    ExtMap { "txt",     "text/plain" },
    ExtMap { "jpeg",    "image/jpeg" },
    ExtMap { "jpg",     "image/jpeg" },
    ExtMap { "jpe",     "image/jpeg" },
    ExtMap { "gz",      "application/x-gzip" },
    ExtMap { "Z",       "application/x-compress" },
    ExtMap { "js",      "application/x-javascript" },
    ExtMap { "ls",      "application/x-javascript" },
    ExtMap { "mocha",   "application/x-javascript" },
    ExtMap { "tcl",     "application/x-tcl" },
    ExtMap { "sh",      "application/x-sh" },
    ExtMap { "csh",     "application/x-csh" },
    ExtMap { "ai",      "application/postscript" },
    ExtMap { "eps",     "application/postscript" },
    ExtMap { "exe",     "application/octet-stream" },
    ExtMap { "bin",     "application/octet-stream" },
    ExtMap { "gtar",    "application/x-gtar" },
    ExtMap { "tar",     "application/x-tar" },
    ExtMap { "zip",     "application/x-zip-compressed" },
    ExtMap { "sit",     "application/x-stuffit" },
    ExtMap { "shar",    "application/x-shar" },
    ExtMap { "hqx",     "application/mac-binhex40" },
    ExtMap { "rtf",     "application/rtf" },
    ExtMap { "tex",     "application/x-tex" },
    ExtMap { "dvi",     "application/x-dvi" },
    ExtMap { "latex",   "application/x-latex" },
    ExtMap { "texi",    "application/x-texinfo" },
    ExtMap { "texinfo", "application/x-texinfo" },
    ExtMap { "avi",     "video/x-msvideo" },
    ExtMap { "qt",      "video/quicktime" },
    ExtMap { "mov",     "video/quicktime" },
    ExtMap { "mpeg",    "video/mpeg" },
    ExtMap { "mpg",     "video/mpeg" },
    ExtMap { "mpe",     "video/mpeg" },
    ExtMap { "wav",     "audio/x-wav" },
    ExtMap { "aif",     "audio/x-aiff" },
    ExtMap { "aiff",    "audio/x-aiff" },
    ExtMap { "aifc",    "audio/x-aiff" },
    ExtMap { "au",      "audio/basic" },
    ExtMap { "snd",     "audio/basic" },
    ExtMap { "bmp",     "image/x-MS-bmp" },
    ExtMap { "rgb",     "image/x-rgb" },
    ExtMap { "ppm",     "image/x-portable-pixmap" },
    ExtMap { "pgm",     "image/x-portable-graymap" },
    ExtMap { "pbm",     "image/x-portable-bitmap" },
    ExtMap { "pnm",     "image/x-portable-anymap" },
    ExtMap { "wxd",     "image/x-xwindowdump" },
    ExtMap { "xpm",     "image/x-xpixmap" },
    ExtMap { "xbm",     "image/x-xbitmap" },
    ExtMap { "ras",     "image/x-cmu-raster" },
    ExtMap { "ief",     "image/ief" },
    ExtMap { "tiff",    "image/tiff" },
    ExtMap { "tif",     "image/tiff" },
    ExtMap { "",        "text/plain" }   (* default kind = text *)
  };

REVEAL
  T = Tx BRANDED "FileNode.T" OBJECT
    kind : FileKind := 0;
  OVERRIDES
    class    := Class;
    iterate  := Iterate;
    next     := Next;
    gen_page := GenPage;
  END;

PROCEDURE Class (<*UNUSED*> t: T): Node.Class =
  BEGIN
    RETURN Node.Class.RawFile;
  END Class;

PROCEDURE Iterate (<*UNUSED*> t: T;  <*UNUSED*> VAR s: Node.IteratorState) =
  BEGIN
  END Iterate;

PROCEDURE Next (<*UNUSED*> t: T;  <*UNUSED*> VAR s: Node.IteratorState): BOOLEAN =
  BEGIN
    RETURN FALSE;
  END Next;

PROCEDURE GenPage (t: T;  wx: Wx.T;
                   <*UNUSED*> action: ID.T;
                   <*UNUSED*> data: Node.FormData)
  RAISES {Wr.Failure, Thread.Alerted} =
  BEGIN
    IF (t.kind = 0) THEN SetKind (t); END;
    wx.put ("Content-type: ", FileMap[t.kind].file_type, "\n");
    (** HTML.GenLocation (t, wx); **)
    IF ConfigItem.X [ConfigItem.T.Use_multiple_windows].bool THEN
      wx.put ("Window-target: ", Node.ClassWindow [Node.Class.RawFile], "\n");
    END;
    wx.put ("\n"); (* end of HTTP header *)
    CopyFile (t, t.path, wx, t.kind);
  END GenPage;

PROCEDURE EmitFile (n: Node.T;  path: TEXT;  wx: Wx.T): BOOLEAN
  RAISES {Wr.Failure, Thread.Alerted} =
  VAR kind := FindKind (path);
  BEGIN
    IF (kind = LAST (FileKind)) THEN (*unknown type*) RETURN FALSE; END;
    wx.put ("Content-type: ", FileMap[kind].file_type, "\n");
    IF ConfigItem.X [ConfigItem.T.Use_multiple_windows].bool THEN
      wx.put ("Window-target: ", Node.ClassWindow [Node.Class.RawFile], "\n");
    END;
    wx.put ("\n"); (* end of HTTP header *)
    CopyFile (n, path, wx, kind);
    RETURN TRUE;
  END EmitFile;

PROCEDURE SetKind (t: T) =
  BEGIN
    t.kind := FindKind (ID.ToText (t.name));
  END SetKind;

PROCEDURE FindKind (nm: TEXT): FileKind =
  VAR ext := Pathname.LastExt (nm);
  BEGIN
    FOR k := 1 TO LAST (FileKind) DO
      IF OS.FileNameEq (ext, FileMap[k].extension) THEN RETURN k; END;
    END;
    RETURN LAST (FileKind);
  END FindKind;

PROCEDURE CopyFile (n: Node.T;  path: TEXT;  wx: Wx.T;  kind: FileKind)
  RAISES {Wr.Failure, Thread.Alerted} =
  (* This procedure modified by R.Coleburn on 2008_0620 to add copyright legend to static HTML pages
  in order to comply with open-source release licensing requirements specified by Farshad Nayeri. *)
  VAR
    rd  := OS.OpenRd (path);
    len : INTEGER;
    buf : ARRAY [0..1023] OF CHAR;
    first_time := TRUE;
    fileIsHTML := (FirstHTML <= kind) AND (kind <= LastHTML);
    start, i : CARDINAL;
    endBody_UC := ARRAY [0..5] OF CHAR {'<', '/', 'B', 'O', 'D', 'Y'};
    endBody_LC := ARRAY [0..5] OF CHAR {'<', '/', 'b', 'o', 'd', 'y'};
    state: CARDINAL := 0;
    found: BOOLEAN := FALSE;
  BEGIN
    IF (rd = NIL) THEN RETURN END;
    (*DebugMsg("!!!RCC::FileNode.CopyFile; name=" & n.printname());*)
    TRY
      TRY
        start := 0;
        LOOP
          len := Rd.GetSub (rd, SUBARRAY(buf, start, (NUMBER(buf) - start))) + start;
          (*DebugMsg("FileNode.CopyFile; read " & Fmt.Int(len - start) & " chars into buf");*)
          start := 0;
          IF (len <= 0) THEN EXIT END;
          IF (first_time) AND fileIsHTML THEN
            start := FixHTML (n, SUBARRAY (buf, start, len), wx);
            DEC(len, start);
            IF (len <= 0) THEN EXIT END;
            (*DebugMsg("FileNode.CopyFile; after FixHTML, start=" & Fmt.Int(start));*)
            first_time := FALSE;
          END;

          IF (NOT found) AND fileIsHTML
          THEN (* add copyright legend before "/body" to comply with Farshad's licensing requirement *)
             i := start;
             LOOP
                WITH c = buf[i]
                DO
                  IF (c = endBody_UC[state]) OR (c = endBody_LC[state])
                  THEN (* match so far *)
                     INC(state);
                     IF state >= NUMBER(endBody_UC)
                     THEN (* a complete match *)
                        found := TRUE;
                        (*DebugMsg("FileNode.CopyFile; found, start=" & Fmt.Int(start) & ", i=" & Fmt.Int(i) & ", len=" & Fmt.Int(len) & ", state=" & Fmt.Int(state));*)

                        (* write the part of buffer preceeding the match *)
                        WITH num = i - start - NUMBER(endBody_UC) + 1
                        DO
                           wx.putStr (SUBARRAY (buf, start, num));
                           DEC(len, num);
                           INC(start, num);
                           (*DebugMsg("FileNode.CopyFile; wrote " & Fmt.Int(num) & " chars from buf, start=" & Fmt.Int(start) & ", i=" & Fmt.Int(i) & ", len=" & Fmt.Int(len));*)
                        END; (* with *)

                        (* write the copyright *)
                        HTML.GenCopyright(wx);
                        (*DebugMsg("FileNode.CopyFile; added copyright for " & n.printname());*)

                        (* write the end-body tag and the rest of the buffer *)
                        IF len > 0
                        THEN
                           wx.putStr (SUBARRAY (buf, start, len));
                           (*DebugMsg("FileNode.CopyFile; emptied " & Fmt.Int(len) & " chars from buf");*)
                        END; (* if *)

                        (* done with this buffer *)
                        start := 0;
                        state := 0;
                        EXIT;
                     END; (* if *)
                  ELSE (* no match *)
                     state := 0;
                  END; (* if *)
                END; (* with *)
                INC(i);
                IF i >= len
                THEN (* exhausted this buffer, so write it out and prepare to read next buffer *)
                   (*DebugMsg("FileNode.CopyFile; buffer exhausted, start=" & Fmt.Int(start) & ", i=" & Fmt.Int(i) & ", len=" & Fmt.Int(len) & ", state=" & Fmt.Int(state));*)
                   wx.putStr (SUBARRAY (buf, start, (len - state)));
                   IF state > 0
                   THEN (* we've seen part of the end-tag, so put this part back in the buffer for the next round *)
                      SUBARRAY (buf, 0, state) := SUBARRAY(endBody_UC, 0, state);
                      start := state;
                   ELSE
                      start := 0;
                   END; (* if *)
                   EXIT;
                END; (* if *)
             END; (* loop *)
          ELSE
             wx.putStr (SUBARRAY (buf, start, len));
          END;
        END;
      FINALLY
        OS.CloseRd (rd);
      END;
    EXCEPT Rd.Failure (ec) =>
      ErrLog.Msg ("Read failed on \"", path, "\"", OS.Err (ec));
    END;
  END CopyFile;

CONST
  HeadMarks  = ARRAY OF TEXT { "</HEAD>", "</head>", "<BODY>", "<body>" };
  TitleMarks = ARRAY OF TEXT { "</H1>","</h1>","</H2>","</h2>","</H3>","</h3>" };
  BaseMarks  = ARRAY OF TEXT { "<BASE", "<base" };

PROCEDURE FixHTML (n: Node.T;  READONLY buf: ARRAY OF CHAR;  wx: Wx.T): INTEGER
  RAISES {Wr.Failure, Thread.Alerted} =
  (* This procedure modified by R.Coleburn on 2008_0620 to return the
  index position of the start of the buffer that has not been processed yet,
  rather than going ahead and writing this unprocessed part of the buffer. *)
  VAR base_loc, end_head, end_title, done: INTEGER;
  BEGIN
    IF (NUMBER (buf) <= 0) THEN RETURN 0 END;

    end_head  := FindMark (buf, HeadMarks);
    base_loc  := FindMark (buf, BaseMarks);
    end_title := FindMark (buf, TitleMarks);

    done := 0;
    IF (n # NIL) AND (base_loc < 0) AND (end_head >= 0) THEN
      (* the file doesn't have a <BASE> tag and we know where to put one! *)
      wx.putStr (SUBARRAY (buf, 0, end_head));   done := end_head;
      HTML.GenBase (n, wx, leaf := TRUE);
    END;

    IF (n # NIL) AND (end_title > done) THEN
      (* we found a title, let's add a pathfinder after it *)
      wx.putStr (SUBARRAY (buf, done, end_title + 5 - done));
      done := end_title + 5;
      HTML.GenPathFinder (n, wx);
    END;

    RETURN done;
  END FixHTML;

PROCEDURE FindMark (READONLY buf   : ARRAY OF CHAR;
                    READONLY marks : ARRAY OF TEXT): INTEGER =
  VAR x: INTEGER;
  BEGIN
    FOR i := FIRST (marks) TO LAST (marks) DO
      x := Text2.FindBufSubstring (buf, marks[i]);
      IF (x >= 0) THEN RETURN x; END;
    END;
    RETURN -1;
  END FindMark;

BEGIN
END FileNode.

interface ErrLog is in:


interface HTML is in:


interface ID is in:


interface OS is in:


interface Wx is in: