From d89b325294201d95498ae9d08f102e12ac2f2876 Mon Sep 17 00:00:00 2001 From: Rose Hogenson Date: Sun, 5 Oct 2025 18:00:06 -0700 Subject: Implement a "magic wormhole" file-sharing solution --- tools/notes/notes.go | 351 --------------------------------------------------- 1 file changed, 351 deletions(-) delete mode 100644 tools/notes/notes.go (limited to 'tools/notes/notes.go') diff --git a/tools/notes/notes.go b/tools/notes/notes.go deleted file mode 100644 index 7714a89..0000000 --- a/tools/notes/notes.go +++ /dev/null @@ -1,351 +0,0 @@ -package main - -import ( - "bytes" - "context" - "encoding/gob" - "errors" - "flag" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "time" - - "github.com/google/subcommands" - "gitlab.com/rhogenson/roseh.moe/internal/api" - "golang.org/x/term" -) - -var serverURL = flag.String("url", "https://roseh.moe", "server url") - -const tokenCache = "/dev/shm/roseh.moe-upload-token" - -var errUnavailable = errors.New("unavailable") - -func readGobResp[Response any](req *http.Request) (*Response, error) { - httpResp, err := http.DefaultClient.Do(req) - if err != nil { - return nil, fmt.Errorf("%w: read gob response: http: %s", errUnavailable, err) - } - defer httpResp.Body.Close() - if httpResp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(httpResp.Body) - return nil, fmt.Errorf("read gob response: error status: %s\n%s", httpResp.Status, body) - } - apiResp := new(api.Response) - if err := gob.NewDecoder(httpResp.Body).Decode(apiResp); err != nil { - return nil, fmt.Errorf("read gob response: decode response: %s", err) - } - if apiResp.Status != api.Ok { - return nil, fmt.Errorf("read gob response: api error: %s: %s", apiResp.Status, apiResp.Err) - } - resp, ok := apiResp.Ok.(*Response) - if !ok { - return nil, fmt.Errorf("read gob response: invalid response") - } - return resp, nil -} - -func login(ctx context.Context) (*api.LoginResponse, error) { - fmt.Fprint(os.Stderr, "Enter password:") - password, err := term.ReadPassword(int(os.Stdin.Fd())) - fmt.Fprintln(os.Stderr) - if err != nil { - return nil, err - } - req, err := http.NewRequestWithContext(ctx, "POST", *serverURL+"/api/login", nil) - if err != nil { - return nil, err - } - req.Header.Set("Roseh-Password", string(password)) - return readGobResp[api.LoginResponse](req) -} - -func loadToken(ctx context.Context) (string, error) { - token, err := os.ReadFile(tokenCache) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - token, err := login(ctx) - if err != nil { - return "", err - } - os.WriteFile(tokenCache, []byte(token.Token), 0600) - return token.Token, nil - } - return "", err - } - return string(token), nil -} - -func gobReq(ctx context.Context, method, url string, req any) (*http.Request, error) { - buf := new(bytes.Buffer) - if err := gob.NewEncoder(buf).Encode(req); err != nil { - return nil, err - } - httpReq, err := http.NewRequestWithContext(ctx, method, url, buf) - if err != nil { - return nil, err - } - token, err := loadToken(ctx) - if err != nil { - return nil, err - } - httpReq.Header.Set("Roseh-Token", token) - return httpReq, nil -} - -func gobReqResp[Response any](ctx context.Context, method, url string, req any) (*Response, error) { - httpReq, err := gobReq(ctx, method, url, req) - if err != nil { - return nil, err - } - return readGobResp[Response](httpReq) -} - -func listNotes(ctx context.Context, req *api.ListNotesRequest) (*api.ListNotesResponse, error) { - return gobReqResp[api.ListNotesResponse](ctx, "GET", *serverURL+"/api/list-notes", req) -} - -func createNote(ctx context.Context, req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) { - return gobReqResp[api.CreateNoteResponse](ctx, "POST", *serverURL+"/api/create-note", req) -} - -func readNote(ctx context.Context, req *api.ReadNoteRequest) (*gob.Decoder, func() error, error) { - httpReq, err := gobReq(ctx, "GET", *serverURL+"/api/read-note", req) - if err != nil { - return nil, nil, err - } - resp, err := http.DefaultClient.Do(httpReq) - if err != nil { - return nil, nil, err - } - if resp.StatusCode != http.StatusOK { - body, _ := io.ReadAll(resp.Body) - resp.Body.Close() - return nil, nil, fmt.Errorf("error status: %s\n%s", resp.Status, body) - } - return gob.NewDecoder(resp.Body), resp.Body.Close, nil -} - -type loginCommand struct{} - -func (*loginCommand) Name() string { - return "login" -} - -func (*loginCommand) Synopsis() string { - return "/api/login" -} - -func (*loginCommand) Usage() string { - return "makes a request to /api/login for debugging" -} - -func (*loginCommand) SetFlags(*flag.FlagSet) {} - -func (*loginCommand) login(ctx context.Context) error { - token, err := login(ctx) - if err != nil { - return err - } - return os.WriteFile(tokenCache, []byte(token.Token), 0600) -} - -func (c *loginCommand) Execute(ctx context.Context, _ *flag.FlagSet, _ ...any) subcommands.ExitStatus { - if err := c.login(ctx); err != nil { - fmt.Fprintln(os.Stderr, err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -type listCommand struct{} - -func (*listCommand) Name() string { - return "list" -} - -func (*listCommand) Synopsis() string { - return "list notes" -} - -func (*listCommand) Usage() string { - return "list all the notes" -} - -func (*listCommand) SetFlags(*flag.FlagSet) {} - -func (*listCommand) list(ctx context.Context) error { - resp, err := listNotes(ctx, &api.ListNotesRequest{}) - if err != nil { - return err - } - for _, note := range resp.Notes { - fmt.Println(note) - } - return nil -} - -func (c *listCommand) Execute(ctx context.Context, _ *flag.FlagSet, _ ...any) subcommands.ExitStatus { - if err := c.list(ctx); err != nil { - fmt.Fprintln(os.Stderr, err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -type newCommand struct{} - -func (*newCommand) Name() string { - return "new" -} - -func (*newCommand) Synopsis() string { - return "create note" -} - -func (*newCommand) Usage() string { - return "upload a new note" -} - -func (*newCommand) SetFlags(*flag.FlagSet) {} - -type createNoteRequestStreamWriter struct { - ctx context.Context - fileName string - continuationToken []byte -} - -func (w *createNoteRequestStreamWriter) Write(buf []byte) (int, error) { - var ( - resp *api.CreateNoteResponse - err error - ) - delay := time.Second - for i := 0; i < 3; i++ { - resp, err = createNote(w.ctx, &api.CreateNoteRequest{ - ContinuationToken: w.continuationToken, - FileName: w.fileName, - Chunk: buf, - More: true, - }) - if !errors.Is(err, errUnavailable) { - break - } - fmt.Fprintf(os.Stderr, "Warning: %s (retrying)\n", err) - select { - case <-time.After(delay): - case <-w.ctx.Done(): - return 0, w.ctx.Err() - } - delay *= 2 - } - if err != nil { - return 0, err - } - w.fileName = "" - w.continuationToken = resp.ContinuationToken - return len(buf), nil -} - -func (*newCommand) new(ctx context.Context, fileName string) error { - f, err := os.Open(fileName) - if err != nil { - return err - } - defer f.Close() - streamWriter := &createNoteRequestStreamWriter{ctx: ctx, fileName: filepath.Base(fileName)} - if _, err := io.Copy(streamWriter, f); err != nil { - return err - } - resp, err := createNote(ctx, &api.CreateNoteRequest{ContinuationToken: streamWriter.continuationToken}) - if err != nil { - return err - } - fmt.Println(resp.Name) - return nil -} - -func (c *newCommand) Execute(ctx context.Context, fs *flag.FlagSet, _ ...any) subcommands.ExitStatus { - args := fs.Args() - if len(args) != 1 { - fmt.Fprintln(os.Stderr, "usage error") - return subcommands.ExitUsageError - } - if err := c.new(ctx, args[0]); err != nil { - fmt.Fprintln(os.Stderr, err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -type readCommand struct{} - -func (*readCommand) Name() string { - return "read" -} - -func (*readCommand) Synopsis() string { - return "read a note" -} - -func (*readCommand) Usage() string { - return "read a note by name" -} - -func (*readCommand) SetFlags(*flag.FlagSet) {} - -func (*readCommand) read(ctx context.Context, key string) error { - decoder, close, err := readNote(ctx, &api.ReadNoteRequest{Note: key}) - if err != nil { - return err - } - defer close() - for { - apiResp := new(api.Response) - if err := decoder.Decode(apiResp); err != nil { - if errors.Is(err, io.EOF) { - break - } - return err - } - if apiResp.Status != api.Ok { - return fmt.Errorf("%s: %s", apiResp.Status, apiResp.Err) - } - resp, ok := apiResp.Ok.(*api.ReadNoteResponseStream) - if !ok { - return fmt.Errorf("invalid response") - } - fmt.Printf("%s", resp.Chunk) - } - return nil -} - -func (c *readCommand) Execute(ctx context.Context, fs *flag.FlagSet, _ ...any) subcommands.ExitStatus { - args := fs.Args() - if len(args) != 1 { - fmt.Fprintln(os.Stderr, "usage error") - return subcommands.ExitUsageError - } - if err := c.read(ctx, args[0]); err != nil { - fmt.Fprintln(os.Stderr, err) - return subcommands.ExitFailure - } - return subcommands.ExitSuccess -} - -func main() { - subcommands.ImportantFlag("url") - subcommands.Register(subcommands.HelpCommand(), "") - subcommands.Register(subcommands.FlagsCommand(), "") - subcommands.Register(subcommands.CommandsCommand(), "") - subcommands.Register(&loginCommand{}, "") - subcommands.Register(&listCommand{}, "") - subcommands.Register(&newCommand{}, "") - subcommands.Register(&readCommand{}, "") - - flag.Parse() - os.Exit(int(subcommands.Execute(context.Background()))) -} -- cgit v1.3.1