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;
}
|