############################################################################
#
#       File:     webup.icn
#
#       Subject:  A rudimentary web-based updater/patcher.
#
#       Author:   Clint Jeffery
#
#       Date:     30 June 2010
#
############################################################################
#
#   This file is in the public domain.
#
############################################################################
#
# webupdate(URL, title) pulls down files from URL to replace local files
#   with different file sizes or older modified time stamps.
#
# Web site structure is at present: three subdirectories
# ("linux","windows","shared"), each of which has a MANIFEST directory
# containing a list of files; each line of the MANIFEST is of the form
#
# filename filesize timestamp
#
# MANIFEST may also contain blank and commented-out lines beginning with #
#
# At present this is a GUI module that opens its own dialog.
# It is desirable that the updating be separated from the GUI part.
#
############################################################################
#
#  Links: gui
#
############################################################################

global webupsite, webuptitle

procedure webupdate(website: string, title: "web update")
local d
   webupsite := website
   webuptitle := title
   d := dialogtable()
   d.show_modal()
end

class dialogtable()
   method show_modal()
   local d, L, platform, dir, N

      d := UnicronProgressBar()
      d.show_modeless()

      platform :=
$ifdef _UNIX
         "linux"
$else
         "windows"
$endif

      N := 0
      every dir := (platform | "shared") do {
         d.syncdir := dir

         if *(L := d.check()) > 0 then {
            N +:= *L
            handle_updates(L, d)
            }
         }
      if N = 0 then {
         d.message("no files updated")
         delay(500)
         d.dispose()
         }
      else {
         d.message((N/3) || " files updated")
         delay(3500)
         d.dispose()
         }
   end

   method handle_updates(L, d)
   local fn, reason, size, errs, nerrs
      errs := ""
      nerrs := 0
      while fn := pop(L) do {
         reason := pop(L)
         size := pop(L)
         errs ||:= d.update(fn, reason, size) || " "
         }
      if errs ~== "" then {
         d.message("exiting because: " || errs)
         dispatcher.message_loop(d)
         exit(-1)
         }
      d.message("updated " || fn || ", continuing")
      delay(800)
      return
   end
end


$include "guiconst.icn"
class ProgressBar : Component (
   p,
   bar_x, bar_y, bar_w, bar_h
)

   method resize()
      /h_spec := WAttrib(cwin, "fheight") + 2 * DEFAULT_TEXT_Y_SURROUND
      self$Component.resize()
      bar_x := x + DEFAULT_TEXT_X_SURROUND
      bar_y := y + BORDER_WIDTH + 3
      bar_w := w - 2 * DEFAULT_TEXT_X_SURROUND
      bar_h := h - 2 * (BORDER_WIDTH + 3)
   end

   method display(buffer_flag)
   local cw
      EraseRectangle(cbwin,x,y,w,h)
      DrawRaisedRectangle(cbwin, x, y, w, h)
      FillRectangle(cbwin, bar_x, bar_y, bar_w * p / 100.0, bar_h)
      cw := Clone(cbwin, "drawop=reverse")
      center_string(cw, x + w/2, y + h/2, p || "%")
      Uncouple(cw)
      if /buffer_flag then CopyArea(cbwin, cwin, x, y, w, h, x, y)
      return
   end

   method handle_event(e)
      if integer(e) = (&lpress|&rpress|&mpress) &
         in_region() & (&x <= bar_x + bar_w) then {
         set_percentage((100 * (&x - bar_x)) / bar_w)
         return _Event(e, self, 0)
         }
   end

   method get_percentage()
      return p
   end

   method set_percentage(pct)
     p := pct
     redisplay()
   end
initially(argv[])
   self$Component.initially()
   set_percentage(0)
   if *argv > 0 then set_fields(argv)
end

class UnicronProgressBar : _Dialog(lab, fnlab, pb, closebtn, syncdir)
   method dialog_event(ev)
      case ev.get_component() of {
         pb :
            write("pb produced an event - percentage = ", pb.get_percentage())
         closebtn: {
            if ev.get_code() > 0 then {
               dispose()
               win := &null
               }
            }
      }
   end
   method message(s)
      lab.set_label(s)
      if \ (is_open) then {
         lab.resize()
         display()
         }
   end
   method check()
   local platform, url, db, filen, size, mtime, L, r, fs
      url := distfile("MANIFEST")
      message("checking "|| url|| "...")
      db := open(url, "m") | {
         write("can't update, was trying to read ", image(url))
         fail
         }
      message("checking... opened " || url)
      L := []
      while r := read(db)  do {
         if (r == "") | (r[1] == "#") | match("filename ",r) then next
         r ? {
            filen := tab(upto(' \t'))|write("no filename in ", image(r))
            tab(many(' \t'))
            size := numeric(tab(many(&digits)))|
                write("no filesize in ", image(r))
            tab(many(' \t'))
            mtime := numeric(tab(many(&digits)))|
                write("no mtime in ", image(r))
            }
         message("checking " || filen)
         if not (fs := stat(filen)) then {
            put(L, filen, "because it is a new file", size)
            }
         else if fs.size ~= size then {
            put(L, filen, "because its size ("||size||
                   ") is different than ours (" || fs.size||")", size)
            }
         else if fs.mtime < mtime then {
            put(L, filen, "because it is newer ("||mtime||
                ") than ours (" || fs.mtime || ")", size)
         }
         else {
            #write("don't think we updated ", filen)
            #write("\tbecause ", image(fs.size), " = ", image(size))
            #write("\tbecause ", image(fs.mtime), " >= ", image(mtime))
            }
         }
      close(db)
      return L
   end
   method distfile(fn)
      return "http://" || webupsite || syncdir || "/" || fn
   end
   method update(fn, reason, size)
   local fetcher, s, fo, newfile, percent, total
      if /win then stop("can't update ", fn, " win is gone")
      message("updating " || fn || " " || reason)
      WAttrib(win, "pointer=" || ("wait"|"watch"))
      fnlab.set_label(fn)
      pb.set_percentage(0)
      (fetcher := open(distfile(fn), "m")) | {
         s := "can't get contents of " || fn
         message(s)
         WAttrib(win, "pointer=arrow")
         return s
         }
      message("httpget (" || image(fetcher) || ")")
      if not (fo := datopen(fn, "wu")) then {
         fn := "new-" || fn
         if not (fo := datopen(fn, "wu")) then {
            s := "can't open "||fn||" for writing"
            message(s)
            WAttrib(win, "pointer=arrow")
            return s
            }
         }
      total := 0
      while writes(fo, s := reads(fetcher, 1024)) do {
         total +:= *s
         percent := 100 * total / size
         if percent > 100 then percent := 100
         pb.set_percentage(percent)
         }
      close(fo)
      if match("new-", fn) then {
         s := "couldn't write " || fn[4:0] ||
               "; close ALL programs and rerun unicron to patch"
         message(s)
         newfile := datopen("new-files","a")
         write(newfile, fn)
         close(newfile)
         WAttrib(win, "pointer=arrow")
         return s
         }
      else
         message("updated " || fn)
      WAttrib(win, "pointer=arrow")
   end

initially
local t
   self$_Dialog.initially()
   set_attribs("label=" || webuptitle, "size=644,399",
               "font=sans", "bg=yellowish white", "resize=on")
   t := Label("pos=10%,15%", "size=80%,10%",
              "internal_alignment=c",
              "label=Checking for update files! please wait...")
   t.set_attribs("font=serif,28,bold","fg=blue")
   add(t)
   lab := Label("pos=50%,80%", "size=90%,10%", "align=c,c",
                "label=initializing...")
   lab.set_attribs("font=serif,18,bold")
   add(lab)
   fnlab := Label("pos=50%,38%", "size=50%,15%", "align=c,c",
                  "label=no files updated")
   fnlab.set_attribs("font=serif,18,bold")
   add(fnlab)
   pb := ProgressBar("pos=50%,50%", "size=50%", "align=c,c")
   add(pb)
   closebtn := TextButton("label=Close", "pos=50%,66%", "align=c,c")
   add(closebtn)
   set_focus(closebtn)
end
