implement Oxedit; include "mods.m"; msg, newedit: import oxex; tidgen := 0; pidgen := 0; init(d: Oxdat) { initmods(d->mods); } Edit.text(ed: self ref Edit): string { text: string; pick edp := ed { Dir => text = sprint("d %s [%s]", ed.path, ed.dir); Msg => text = sprint("m %s [%s]", ed.path, ed.dir); File => text = sprint("f %s [%s]", ed.path, ed.dir); if (edp.dirty) text += " dirty"; else text += " clean"; } return text; } Tree.new(path: string): ref Tree { tid := tidgen++; if (debug) fprint(stderr, "o/x: new tree %s id %d\n", path, tid); tr := ref Tree(tid, path, nil, nil, nil, nil); trees = tr::trees; return tr; } Tree.find(tid: int): ref Tree { for (trl := trees; trl != nil; trl = tl trl){ tr := hd trl; if (tr.tid == tid) return tr; } return nil; } Tree.close(tr: self ref Tree) { edl := tr.eds; # File.close will try to tr.deledit() tr.eds = nil; # Avoid that while we are scanning that list. for (; edl != nil; edl = tl edl) (hd edl).close(); if (tr.col != nil){ tr.col.close(); tr.col = nil; } if (tr.tag != nil){ tr.tag.close(); tr.tag = nil; } tr.tid = -1; # posion nl: list of ref Tree; nl = nil; for (trl := trees; trl != nil; trl = tl trl) if (hd trl != tr) nl = hd trl :: nl; if (debug) fprint(stderr, "o/x: close tree %s\n", tr.path); trees = nl; } Tree.mk(tr: self ref Tree) { id := ++pidgen; tr.col = ui.new("col:tree", id); if (tr.col == nil) error("o/x: ui.new"); tr.tag = tr.col.new("tag:tree", id); if (tr.tag == nil) error("o/x: ui.new"); fd := open(tr.tag.path+"/data", OWRITE|OTRUNC); if (fd == nil) error("o/x: tag open"); text := sprint("Ox %d", pctl(0, nil)); text += sprint(" %s End Dup ", tr.path); data := array of byte text; if (write(fd, data, len data) != len data) error("o/x: tag write"); tr.xtag = tr.col.new("tag:cmds", id); if (tr.xtag == nil) error("o/x: ui.new"); fd = open(tr.xtag.path+"/data", OWRITE|OTRUNC); if (fd == nil) error("o/x: tag open"); data = array of byte "no cmds"; if (write(fd, data, len data) != len data) error("o/x: tag write"); } Tree.findedit(tr: self ref Tree, name: string): ref Edit { for (edl := tr.eds; edl != nil; edl = tl edl){ ed := hd edl; if (ed.path == name) return ed; } return nil; } lrul(edl: list of ref Edit, k: int): (ref Edit, int) { led: ref Edit; led = nil; ltime := now(); n := 0; for (; edl != nil; edl = tl edl){ ed := hd edl; if (tagof(ed) == k){ n++; if (!ed.keep && !ed.dirty && ed.lru < ltime){ led = ed; ltime = ed.lru; } } } return (led, n); } # No more than 2 dirs in a tree, no more than 6 edits. # For files, it would be nice to keep a global policy in # the entire omero regarding when to close forgotten panels. # Also, when to minimize them Tree.lru(tr: self ref Tree) { Ndirs: con 2; Neds: con 6; Nmsgs: con 2; (ed, n) := lrul(tr.eds, tagof(Edit.Dir)); if (ed != nil && n > Ndirs) ed.close(); (ed, n) = lrul(tr.eds, tagof(Edit.Msg)); if (ed != nil && n > Nmsgs){ ed.close(); n--; } nm := n; (ed, n) = lrul(tr.eds, tagof(Edit.File)); if (ed != nil && n + nm > Neds) ed.close(); } Tree.addedit(tr: self ref Tree, ed: ref Edit) { tr.lru(); tr.eds = ed::tr.eds; } Tree.deledit(tr: self ref Tree, ed: ref Edit) { nl: list of ref Edit; nl = nil; for (edl := tr.eds; edl != nil; edl = tl edl){ if (hd edl != ed) nl = hd edl :: nl; } tr.eds = nl; } Tree.dump(tr: self ref Tree) { fprint(stderr, "tree %s\n", tr.path); for (edl := tr.eds; edl != nil; edl = tl edl) fprint(stderr, "\t%s\n", (hd edl).text()); } # Precaution. Copy the file being edited to /tmp, just in case we # screw things up safe(name: string) { fname := "/tmp/" + names->basename(name, nil); fd := create(fname, OWRITE, 8r664); sfd := open(name, OREAD); if (fd == nil || sfd == nil || panels->copy(fd, sfd) < 0) fprint(stderr, "o/x: can't backup %s: %r\n", fname); } msgpath(n: string): string { (p, nil) := splitl(n[1:], "] "); if (p == nil || p == "") p = n[1:]; return p; } Edit.new(name: string, tid: int, msg: int): ref Edit { ed: ref Edit; if (msg){ path := name; if (name[0] == '[') path = msgpath(name); else name = sprint("[%s]", name); ed = ref Edit.Msg(name, path, tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0); } else { (e, d) := stat(name); if (e < 0) return nil; if (d.qid.qtype&QTDIR) ed = ref Edit.Dir(name, name, tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0, Qid(big 0, 0, QTDIR)); else { safe(name); ed = ref Edit.File(name, names->dirname(name), tid, 0, 0, now(), nil, nil, nil, nil, nil, nil, 0, 0, 0, Qid(big 0, 0, 0), nil); } } if (debug) fprint(stderr, "o/x: new edit %s tid %d\n", name, tid); return ed; } Edit.close(ed: self ref Edit) { tid := ed.tid; if (tid == -1) # already closed return; ed.tid = -1; if (ed.col != nil) ed.col.close(); if (ed.tag != nil) ed.tag.close(); if (ed.body != nil) ed.body.close(); ed.col = ed.tag = ed.body = nil; if (debug) fprint(stderr, "o/x: close edit %s tid %d\n", ed.path, ed.tid); tr := Tree.find(tid); tr.deledit(ed); } showedit(ed: ref Edit) { scr := hd panels->screens(); col := hd tl panels->cols(scr); ed.col.ctl(sprint("copyto %s\n", col)); } Edit.mk(ed: self ref Edit) { col: ref Panel; tagtext := ""; body := "text:file"; id := ++pidgen; sed : ref Edit; sed = nil; pick edp := ed { File => col = edp.col = ui.new("col:ox", id); sed = edp; Msg => col = edp.col = ui.new("col:ox", id); sed = edp; if (edp.col == nil) error(sprint("o/x: ui.new: %r")); Dir => tr := Tree.find(ed.tid); col = tr.col; tagtext += " Dup / .."; body = "tbl:body"; } tagtext += " | "; if (col == nil) error(sprint("o/x: ui.new: %r")); ed.tag = col.new("tag:file", id); ed.body = col.new(body, id); if (ed.tag == nil || ed.body == nil) error(sprint("o/x: ui.new: %r")); fd := open(ed.tag.path+"/data", OWRITE|OTRUNC); if (fd == nil) error (sprint("o/x: new: %r\n")); data := array of byte sprint("%s %s", ed.path, tagtext); if (write(fd, data, len data) != len data) error (sprint("o/x: new: write: %r\n")); if (sed != nil) showedit(sed); } Edit.put(ed: self ref Edit, where: string): int { ffd: ref FD; tr := Tree.find(ed.tid); pick edp := ed { File => if (where != ed.path) ffd = create(where, OWRITE, 8r664); else { if (edp.dirty == 0) # nothing to put return 0; ffd = open(where, OWRITE|OTRUNC); } if (ffd == nil){ msg(tr, ed.dir, sprint("%s: %r\n", where)); return -1; } fd := open(ed.body.path+"/data", OREAD); if (fd == nil){ msg(tr, ed.dir, sprint("open %s/data: %r\n", ed.body.path)); return -1; } if (panels->copy(ffd, fd) < 0){ msg(tr, ed.dir, sprint("put %s: %r\n", where)); return -1; } if (debug) fprint(stderr, "o/x: put %s\n", where); ffd = nil; fd = nil; # forze stat to see new qid if (where == ed.path){ (e, d) := stat(ed.path); if (e < 0){ msg(tr, ed.dir, sprint("stat %s: %r\n", ed.path)); return -1; } edp.qid = d.qid; edp.dirty = 0; edp.body.ctl("clean"); } else msg(tr, ed.dir, sprint("%s: new file\n", where)); } return 0; } Edit.get(ed: self ref Edit): int { if (tagof(ed) == tagof(Edit.Msg)) return 0; tr := Tree.find(ed.tid); fd := open(ed.body.path+"/data", OWRITE|OTRUNC); if (fd == nil){ msg(tr, ed.dir, sprint("open: %s/data: %r\n", ed.body.path)); return -1; } pick edp := ed { File => ffd := open(ed.path, OREAD); if (panels->copy(fd, ffd) < 0){ msg(tr, ed.dir, sprint("get %s: %r\n", ed.path)); return -1; } (e, d) := fstat(ffd); if (e < 0){ msg(tr, ed.dir, sprint("fstat %s: %r\n", ed.path)); return -1; } edp.qid = d.qid; edp.dirty = 0; edp.body.ctl("clean"); Dir => (dirs, n) := readdir->init(ed.path, NAME); if (n < 0){ msg(tr, ed.dir, sprint("open: %s: %r\n", ed.path)); return -1; } else if (n == 0) fprint(fd, "none\n"); else { text := ""; for (i := 0; i < n; i++) text += dirs[i].name + "\n"; data := array of byte text; if (write(fd, data, len data) != len data){ msg(tr, ed.dir, sprint("write: %s/data: %r\n", ed.body.path)); return -1; } } } if (debug) fprint(stderr, "o/x: get %s\n", ed.path); return 0; } Edit.getedits(ed: self ref Edit) { if (ed == nil || ed.buf != nil) return; fd := open(ed.body.path + "/data", OREAD); if (fd != nil && (s := readfile(fd)) != nil) ed.buf = Blks.new(string s); else ed.buf = Blks.new(""); # getsel really belongs to panel.b in lib. getattrs() should read and # parse the contents of ctl. # (ed.q0, ed.q1) = getsel(ed.body); ed.edited = 0; } Edit.clredits(ed: self ref Edit) { ed.buf = nil; ed.edited = 0; } Edit.putedits(ed: self ref Edit) { if (tagof(ed) == tagof(Edit.File) || tagof(ed) == tagof(Edit.Msg)){ ctl := ""; if (ed.edited&Etext){ fd := open(ed.body.path+"/data", OWRITE|OTRUNC); if (fd != nil){ s := ed.buf.pack(); d := array of byte s.s; write(fd, d, len d); } if (!ed.dirty){ ed.dirty = 1; ctl = "dirty\n"; } } if (ed.edited&Esel) ctl += sprint("sel %d %d\n", ed.q0, ed.q1); if (ctl != "") ed.body.ctl(ctl); } ed.clredits(); } findpanel(id: int): (ref Tree, ref Edit) { for (trl := trees; trl != nil; trl = tl trl){ tr := hd trl; for (edl := tr.eds; edl != nil; edl = tl edl){ ed := hd edl; if (ed.tag != nil && ed.tag.id == id) return (tr, ed); } if (tr.col != nil && tr.col.id == id) return (tr, nil); } return (nil, nil); } Edit.cleanto(ed: self ref Edit, cmd: string, arg: string): string { pick edp := ed { File => if (edp.lastcmd == cmd) return nil; edp.lastcmd = cmd; case cmd { "Put" => (e, d) := stat(ed.path); if (e < 0) return sprint("stat: %r"); if (d.qid.path != edp.qid.path || d.qid.vers != edp.qid.vers) return sprint("file changed by %s", d.muid); "New" => (e, nil) := stat(arg); if (e >= 0) return sprint("%s: file exists", arg); * => if (edp.dirty) return "put changes first"; } } return nil; } Elogbuf.new(): ref Elogbuf { return ref Elogbuf(array[0] of ref Elog, 0); } Elogbuf.push(e: self ref Elogbuf, b: ref Elog) { if (e.n == len e.b){ nb := array[len e.b + 16] of ref Elog; nb[0:] = e.b[0:len e.b]; for (i := len e.b; i < len nb; i++) nb[i] = nil; e.b = nb; } e.b[e.n++] = b; } Elogbuf.pop(e: self ref Elogbuf): ref Elog { if (e.n == 0) return nil; b := e.b[--e.n]; e.b[e.n] = nil; # poison return b; }