|
| 1 | +// |
| 2 | +// "Unsee" a feed item |
| 3 | +// |
| 4 | + |
| 5 | +package main |
| 6 | + |
| 7 | +import ( |
| 8 | + "fmt" |
| 9 | + "os" |
| 10 | + "path/filepath" |
| 11 | + |
| 12 | + "github.com/skx/rss2email/state" |
| 13 | + "github.com/skx/subcommands" |
| 14 | + "go.etcd.io/bbolt" |
| 15 | +) |
| 16 | + |
| 17 | +// Structure for our options and state. |
| 18 | +type unseeCmd struct { |
| 19 | + |
| 20 | + // We embed the NoFlags option, because we accept no command-line flags. |
| 21 | + subcommands.NoFlags |
| 22 | +} |
| 23 | + |
| 24 | +// Info is part of the subcommand-API. |
| 25 | +func (s *unseeCmd) Info() (string, string) { |
| 26 | + return "unsee", `Regard a feed item as new, and unseen. |
| 27 | +
|
| 28 | +This sub-command will allow you to mark an item as |
| 29 | +unseen, or new, meaning the next time the cron or daemon |
| 30 | +commands run they'll trigger an email notification. |
| 31 | +
|
| 32 | +This is useful in the case of testing. |
| 33 | +` |
| 34 | +} |
| 35 | + |
| 36 | +// |
| 37 | +// Entry-point. |
| 38 | +// |
| 39 | +func (s *unseeCmd) Execute(args []string) int { |
| 40 | + |
| 41 | + if len(args) < 1 { |
| 42 | + fmt.Printf("Please specify the URLs to unsee\n") |
| 43 | + return 1 |
| 44 | + } |
| 45 | + |
| 46 | + // Ensure we have a state-directory. |
| 47 | + dir := state.Directory() |
| 48 | + os.MkdirAll(dir, 0666) |
| 49 | + |
| 50 | + // Now create the database, if missing, or open it if it exists. |
| 51 | + db, err := bbolt.Open(filepath.Join(dir, "state.db"), 0666, nil) |
| 52 | + if err != nil { |
| 53 | + fmt.Printf("Error opening database: %s\n", err.Error()) |
| 54 | + return 1 |
| 55 | + } |
| 56 | + |
| 57 | + // Ensure we close when we're done |
| 58 | + defer db.Close() |
| 59 | + |
| 60 | + // Keep track of buckets here |
| 61 | + var bucketNames [][]byte |
| 62 | + |
| 63 | + // Record each bucket |
| 64 | + db.View(func(tx *bbolt.Tx) error { |
| 65 | + tx.ForEach(func(bucketName []byte, _ *bbolt.Bucket) error { |
| 66 | + bucketNames = append(bucketNames, bucketName) |
| 67 | + return nil |
| 68 | + }) |
| 69 | + return nil |
| 70 | + }) |
| 71 | + |
| 72 | + // Process each bucket to find the item to remove. |
| 73 | + for _, buck := range bucketNames { |
| 74 | + |
| 75 | + err = db.Update(func(tx *bbolt.Tx) error { |
| 76 | + |
| 77 | + // Items to remove |
| 78 | + remove := []string{} |
| 79 | + |
| 80 | + // Select the bucket, which we know must exist |
| 81 | + b := tx.Bucket([]byte(buck)) |
| 82 | + |
| 83 | + // Get a cursor to the key=value entries in the bucket |
| 84 | + c := b.Cursor() |
| 85 | + |
| 86 | + // Iterate over the key/value pairs. |
| 87 | + for k, _ := c.First(); k != nil; k, _ = c.Next() { |
| 88 | + |
| 89 | + // Convert the key to a string |
| 90 | + key := string(k) |
| 91 | + |
| 92 | + // Is this something to remove? |
| 93 | + for _, arg := range args { |
| 94 | + |
| 95 | + // If so append it. |
| 96 | + // |
| 97 | + // TODO: regexp? |
| 98 | + if arg == key { |
| 99 | + remove = append(remove, key) |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + } |
| 104 | + |
| 105 | + // Now remove |
| 106 | + for _, key := range remove { |
| 107 | + b.Delete([]byte(key)) |
| 108 | + } |
| 109 | + return nil |
| 110 | + }) |
| 111 | + |
| 112 | + if err != nil { |
| 113 | + fmt.Printf("error iterating over bucket %s: %s\n", buck, err.Error()) |
| 114 | + return 1 |
| 115 | + } |
| 116 | + } |
| 117 | + |
| 118 | + return 0 |
| 119 | +} |
0 commit comments