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  }