github.com/claudiodangelis/banco@v0.0.0-20231219182139-c31d5d844fe5/module/bookmarks/bookmarks.go (about) 1 package bookmarks 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 "time" 12 13 "github.com/claudiodangelis/banco/config" 14 "github.com/claudiodangelis/banco/ui" 15 16 "github.com/claudiodangelis/banco/item" 17 "github.com/otiai10/copy" 18 ) 19 20 // Bookmarks is the module 21 type Bookmarks struct{} 22 23 // Bookmark is a bookmark 24 type Bookmark struct { 25 Title string 26 // TODO: this should be type URL 27 URL string 28 Size int64 29 UpdatedAt time.Time 30 Group string 31 } 32 33 // Whether or not the module has templates 34 func (b Bookmarks) HasTemplates() bool { 35 return false 36 } 37 38 // Aliases of the module 39 func (b Bookmarks) Aliases() []string { 40 return []string{"b", "bm"} 41 } 42 43 // Path of the bookmark 44 func (b Bookmark) Path() string { 45 return filepath.Join("bookmarks", b.Group, b.Title) 46 } 47 48 // Name of the module 49 func (b Bookmarks) Name() string { 50 return "bookmarks" 51 } 52 53 // Singular name of the module 54 func (b Bookmarks) Singular() string { 55 return "bookmark" 56 } 57 58 // Init the module 59 func (b Bookmarks) Init() error { 60 // Create "notes" directory 61 if err := os.Mkdir("bookmarks", os.ModePerm); err != nil { 62 return err 63 } 64 return nil 65 } 66 67 // UpdateItemParameters when updating a note 68 func (b Bookmarks) UpdateItemParameters(current item.Item) []item.Parameter { 69 parameters := []item.Parameter{} 70 for _, parameter := range b.NewItemParameters() { 71 parameter.Default = current[parameter.Name] 72 parameters = append(parameters, parameter) 73 } 74 return parameters 75 } 76 77 // NewItemParameters for a new bookmark 78 func (b Bookmarks) NewItemParameters() []item.Parameter { 79 allGroups, err := groups() 80 if err != nil { 81 panic(err) 82 } 83 return []item.Parameter{ 84 { 85 Name: "Title", 86 Default: "", 87 InputType: ui.InputText, 88 }, 89 { 90 Name: "URL", 91 Default: "", 92 InputType: ui.InputText, 93 }, 94 { 95 Name: "Group", 96 Default: "", 97 InputType: ui.InputSelectWithAdd, 98 Options: allGroups, 99 }, 100 } 101 } 102 103 // DeleteItem from disk 104 func (b Bookmarks) DeleteItem(item item.Item) error { 105 return delete(toBookmark(item)) 106 } 107 108 // save bookmark to disk 109 func save(bookmark Bookmark) error { 110 if bookmark.Group != "" { 111 if err := os.MkdirAll("bookmarks/"+bookmark.Group, os.ModePerm); err != nil { 112 return err 113 } 114 } 115 filename := filepath.Join("bookmarks", bookmark.Group, bookmark.Title) 116 if _, err := os.Stat(filename); err == nil { 117 return errors.New("file already exists") 118 } else if !os.IsNotExist(err) { 119 return err 120 } 121 f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644) 122 if err != nil { 123 return err 124 } 125 defer f.Close() 126 if _, err := f.WriteString(bookmark.URL); err != nil { 127 return err 128 } 129 return nil 130 } 131 132 // UpdateItem updates current item to next item 133 func (b Bookmarks) UpdateItem(currentItem, nextItem item.Item) error { 134 current := toBookmark(currentItem) 135 next := toBookmark(nextItem) 136 // create next directory 137 if err := os.MkdirAll(filepath.Join("bookmarks", next.Group), os.ModePerm); err != nil { 138 return err 139 } 140 // check if file in the next directory already exists with that name 141 if _, err := os.Stat(filepath.Join("bookmarks", next.Group, next.Title)); err == nil { 142 return errors.New("a bookmark already exists with that name") 143 } 144 // copy current note to the next directory 145 if err := copy.Copy(current.Path(), next.Path()); err != nil { 146 return err 147 } 148 // delete old file (and it's parent, if empty) 149 if err := delete(current); err != nil { 150 return err 151 } 152 return nil 153 } 154 155 // delete a bookmark 156 func delete(bookmark Bookmark) error { 157 // TODO: We should have a proper function to check if it exists 158 if bookmark.Title == "" { 159 return errors.New("bookmark does not exist") 160 } 161 // Delete the bookmark if it exists 162 if err := os.Remove(bookmark.Path()); err != nil { 163 return err 164 } 165 // If directory is empty, delete directory 166 contents, err := ioutil.ReadDir(filepath.Dir(bookmark.Path())) 167 if err != nil { 168 return err 169 } 170 if len(contents) > 0 { 171 // Directory is not empty 172 return nil 173 } 174 // Recursively check if group and its parents are empty, if so, delete them 175 dir := filepath.Dir(bookmark.Path()) 176 for { 177 if err := os.Remove(dir); err != nil { 178 // TODO: this is not the strongest option 179 return nil 180 } 181 // TODO: This is a bug: what if there is a group called "bookmarks"? 182 // TODO: This bug exists in "notes" module as well 183 dir = filepath.Dir(dir) 184 if dir == "bookmarks" { 185 break 186 } 187 } 188 return nil 189 } 190 191 // SaveItem saves the bookmark 192 func (b Bookmarks) SaveItem(item item.Item) error { 193 // Convert to bookmark 194 bookmark := toBookmark(item) 195 // Save it 196 err := save(bookmark) 197 return err 198 } 199 200 // groups returns a list of groups 201 func groups() ([]string, error) { 202 bookmarks, err := list() 203 if err != nil { 204 return []string{}, err 205 } 206 m := make(map[string]bool) 207 for _, bookmark := range bookmarks { 208 m[bookmark.Group] = true 209 } 210 groups := []string{} 211 for group := range m { 212 groups = append(groups, group) 213 } 214 return groups, nil 215 } 216 217 // list all items 218 func list() ([]Bookmark, error) { 219 // Read dir 220 var bookmarks []Bookmark 221 if _, err := os.Stat("bookmarks"); os.IsNotExist(err) { 222 return bookmarks, err 223 } 224 if err := filepath.Walk("bookmarks", func(path string, info os.FileInfo, fnerr error) error { 225 if info.IsDir() { 226 return nil 227 } 228 bookmark := Bookmark{ 229 Title: info.Name(), 230 Size: info.Size(), 231 UpdatedAt: info.ModTime(), 232 } 233 group := filepath.Dir(strings.TrimPrefix(path, "bookmarks/")) 234 if group != "." { 235 bookmark.Group = group 236 } 237 // Read URL 238 url, err := ioutil.ReadFile(bookmark.Path()) 239 if err != nil { 240 return err 241 } 242 bookmark.URL = string(url) 243 bookmarks = append(bookmarks, bookmark) 244 return nil 245 }); err != nil { 246 return bookmarks, err 247 } 248 return bookmarks, nil 249 } 250 251 func getBrowserConfiguration() (cmd string, args []string) { 252 cfg := config.New() 253 // Read config file first 254 cmd = cfg.Get("bookmarks.browser.cmd") 255 args = cfg.GetStrings("bookmarks.browser.args") 256 if cmd == "" { 257 cmd = os.Getenv("BROWSER") 258 } 259 return cmd, args 260 } 261 262 // OpenItem with $BROWSER 263 func (b Bookmarks) OpenItem(item item.Item) error { 264 bookmark := toBookmark(item) 265 browsercmd, browserargs := getBrowserConfiguration() 266 if browsercmd == "" { 267 return errors.New("$BROWSER variable not set and no browser configured") 268 269 } 270 browserargs = append(browserargs, bookmark.URL) 271 cmd := exec.Command(browsercmd, browserargs...) 272 cmd.Stdin = os.Stdin 273 cmd.Stdout = os.Stdout 274 if err := cmd.Start(); err != nil { 275 return err 276 } 277 return nil 278 } 279 280 // toBookmark converts an item to a bookmark 281 func toBookmark(i item.Item) Bookmark { 282 bookmark := Bookmark{} 283 bookmark.Title = i["Title"] 284 bookmark.Group = i["Group"] 285 bookmark.URL = i["URL"] 286 return bookmark 287 } 288 289 // toItem converts Bookmark to item.Item 290 func toItem(bookmark Bookmark) item.Item { 291 i := make(item.Item) 292 i["Title"] = bookmark.Title 293 i["Group"] = bookmark.Group 294 i["URL"] = bookmark.URL 295 i["Name"] = filepath.Join(bookmark.Group, bookmark.Title) 296 return i 297 } 298 299 // all toItemAll converts all Bookmarks to item.Items 300 func toItemAll(bookmarks []Bookmark) []item.Item { 301 items := []item.Item{} 302 for _, bookmark := range bookmarks { 303 items = append(items, toItem(bookmark)) 304 } 305 return items 306 } 307 308 // List items 309 func (b Bookmarks) List() ([]item.Item, error) { 310 bookmarks, err := list() 311 if err != nil { 312 return nil, err 313 } 314 return toItemAll(bookmarks), nil 315 } 316 317 // Summary of items 318 func (b Bookmarks) Summary() string { 319 list, err := list() 320 if err != nil { 321 panic(err) 322 } 323 groups := make(map[string]bool) 324 for _, bookmark := range list { 325 groups[bookmark.Group] = true 326 } 327 return fmt.Sprintf("bookmarks [URLs: %d, groups: %d]", len(list), len(groups)) 328 } 329 330 // Module return the module 331 func Module() Bookmarks { 332 return Bookmarks{} 333 }