diff --git a/db/migrations/000001_init.up.sql b/db/migrations/000001_init.up.sql index aa003337..bdaa2dd2 100644 --- a/db/migrations/000001_init.up.sql +++ b/db/migrations/000001_init.up.sql @@ -101,6 +101,7 @@ CREATE TABLE line ( ephemeral boolean NOT NULL, contentheight int NOT NULL, star int NOT NULL, + hidden boolean NOT NULL, PRIMARY KEY (sessionid, windowid, lineid) ); diff --git a/pkg/cmdrunner/cmdrunner.go b/pkg/cmdrunner/cmdrunner.go index 3ebb907d..bc78f4eb 100644 --- a/pkg/cmdrunner/cmdrunner.go +++ b/pkg/cmdrunner/cmdrunner.go @@ -147,6 +147,8 @@ func init() { registerCmdFn("line", LineCommand) registerCmdFn("line:show", LineShowCommand) registerCmdFn("line:star", LineStarCommand) + registerCmdFn("line:hide", LineHideCommand) + registerCmdFn("line:purge", LinePurgeCommand) registerCmdFn("_history", HistoryCommand) @@ -1544,7 +1546,7 @@ func SwResizeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst } func LineCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { - return nil, fmt.Errorf("/line requires a subcommand: %s", formatStrs([]string{"show"}, "or", false)) + return nil, fmt.Errorf("/line requires a subcommand: %s", formatStrs([]string{"show", "star", "hide", "purge"}, "or", false)) } func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { @@ -1588,6 +1590,70 @@ func LineStarCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sst return sstore.ModelUpdate{Line: lineObj}, nil } +func LineHideCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) + if err != nil { + return nil, err + } + if len(pk.Args) == 0 { + return nil, fmt.Errorf("/line:hide requires an argument (line number or id)") + } + lineArg := pk.Args[0] + lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.WindowId, lineArg) + if err != nil { + return nil, fmt.Errorf("error looking up lineid: %v", err) + } + if lineId == "" { + return nil, fmt.Errorf("line %q not found", lineArg) + } + shouldHide := true + if len(pk.Args) >= 2 { + shouldHide = resolveBool(pk.Args[1], true) + } + err = sstore.SetLineHiddenById(ctx, lineId, shouldHide) + if err != nil { + return nil, fmt.Errorf("/line:hide error updating hidden status: %v", err) + } + lineObj, err := sstore.GetLineById(ctx, lineId) + if err != nil { + return nil, fmt.Errorf("/line:star error getting line: %v", err) + } + if lineObj == nil { + // no line (which is strange given we checked for it above). just return a nop. + return nil, nil + } + return sstore.ModelUpdate{Line: lineObj}, nil +} + +func LinePurgeCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { + ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) + if err != nil { + return nil, err + } + if len(pk.Args) == 0 { + return nil, fmt.Errorf("/line:purge requires an argument (line number or id)") + } + lineArg := pk.Args[0] + lineId, err := sstore.FindLineIdByArg(ctx, ids.SessionId, ids.WindowId, lineArg) + if err != nil { + return nil, fmt.Errorf("error looking up lineid: %v", err) + } + if lineId == "" { + return nil, fmt.Errorf("line %q not found", lineArg) + } + err = sstore.PurgeLineById(ctx, ids.SessionId, lineId) + if err != nil { + return nil, fmt.Errorf("/line:purge error purging line: %v", err) + } + lineObj := &sstore.LineType{ + SessionId: ids.SessionId, + WindowId: ids.WindowId, + LineId: lineId, + Remove: true, + } + return sstore.ModelUpdate{Line: lineObj}, nil +} + func LineShowCommand(ctx context.Context, pk *scpacket.FeCommandPacketType) (sstore.UpdatePacket, error) { ids, err := resolveUiIds(ctx, pk, R_Session|R_Screen|R_Window) if err != nil { diff --git a/pkg/sstore/dbops.go b/pkg/sstore/dbops.go index 471eafa6..9fbd0ae0 100644 --- a/pkg/sstore/dbops.go +++ b/pkg/sstore/dbops.go @@ -663,8 +663,8 @@ func InsertLine(ctx context.Context, line *LineType, cmd *CmdType) error { query = `SELECT nextlinenum FROM window WHERE sessionid = ? AND windowid = ?` nextLineNum := tx.GetInt(query, line.SessionId, line.WindowId) line.LineNum = int64(nextLineNum) - query = `INSERT INTO line ( sessionid, windowid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, text, cmdid, ephemeral, contentheight, star) - VALUES (:sessionid,:windowid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:text,:cmdid,:ephemeral,:contentheight,:star)` + query = `INSERT INTO line ( sessionid, windowid, userid, lineid, ts, linenum, linenumtemp, linelocal, linetype, text, cmdid, ephemeral, contentheight, star, hidden) + VALUES (:sessionid,:windowid,:userid,:lineid,:ts,:linenum,:linenumtemp,:linelocal,:linetype,:text,:cmdid,:ephemeral,:contentheight,:star,:hidden)` tx.NamedExecWrap(query, line) query = `UPDATE window SET nextlinenum = ? WHERE sessionid = ? AND windowid = ?` tx.ExecWrap(query, nextLineNum+1, line.SessionId, line.WindowId) @@ -1486,3 +1486,42 @@ func GetLineById(ctx context.Context, lineId string) (*LineType, error) { } return rtn, nil } + +func SetLineHiddenById(ctx context.Context, lineId string, hidden bool) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `UPDATE line SET hidden = ? WHERE lineid = ?` + tx.ExecWrap(query, hidden, lineId) + return nil + }) + return txErr +} + +func purgeCmdById(ctx context.Context, sessionId string, cmdId string) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `DELETE FROM cmd WHERE sessionid = ? AND cmdid = ?` + tx.ExecWrap(query, sessionId, cmdId) + return DeletePtyOutFile(tx.Context(), sessionId, cmdId) + }) + return txErr +} + +func PurgeLineById(ctx context.Context, sessionId string, lineId string) error { + txErr := WithTx(ctx, func(tx *TxWrap) error { + query := `SELECT cmdid FROM line WHERE sessionid = ? AND lineid = ?` + cmdId := tx.GetString(query, sessionId, lineId) + query = `DELETE FROM line WHERE sessionid = ? AND lineid = ?` + tx.ExecWrap(query, sessionId, lineId) + if cmdId != "" { + query = `SELECT count(*) FROM line WHERE sessionid = ? AND cmdid = ?` + cmdRefCount := tx.GetInt(query, sessionId, cmdId) + if cmdRefCount == 0 { + err := purgeCmdById(tx.Context(), sessionId, cmdId) + if err != nil { + return err + } + } + } + return nil + }) + return txErr +} diff --git a/pkg/sstore/fileops.go b/pkg/sstore/fileops.go index 1fdad2cd..5fc689bf 100644 --- a/pkg/sstore/fileops.go +++ b/pkg/sstore/fileops.go @@ -127,3 +127,11 @@ func FullSessionDiskSize() (map[string]SessionDiskSizeType, error) { } return rtn, nil } + +func DeletePtyOutFile(ctx context.Context, sessionId string, cmdId string) error { + ptyOutFileName, err := scbase.PtyOutFile(sessionId, cmdId) + if err != nil { + return err + } + return os.Remove(ptyOutFileName) +} diff --git a/pkg/sstore/sstore.go b/pkg/sstore/sstore.go index efa2636f..ef40f2a3 100644 --- a/pkg/sstore/sstore.go +++ b/pkg/sstore/sstore.go @@ -585,9 +585,10 @@ type LineType struct { Text string `json:"text,omitempty"` CmdId string `json:"cmdid,omitempty"` Ephemeral bool `json:"ephemeral,omitempty"` - Star bool `json:"star,omitempty"` - Remove bool `json:"remove,omitempty"` ContentHeight int64 `json:"contentheight,omitempty"` + Star bool `json:"star,omitempty"` + Hidden bool `json:"hidden,omitempty"` + Remove bool `json:"remove,omitempty"` } type ResolveItem struct {