package engine import ( "context" "sync" "github.com/deranjer/gvc/common/database" watcher "github.com/radovskyb/watcher" "github.com/rs/zerolog" ) type key string type Event struct { Name string Progress int Total int } // The watcher is responsible for not only seeing when a file changes, // but also keeping track of // * the file hash so that if it changes again any modifications can be handled // * copying any versions and keeping them safe (even if temporary) // * creating the diff of the file, in both directions if necessary // * storing the details in the database func NewWatcher(logger *zerolog.Logger, KEYFOLDER, DOWNLOADFOLDER, SYNCFOLDER, THUMBFOLDER, DIFFFOLDER string) (FileWatcher, error) { w := FileWatcher{ watcher.New(), logger, true, //used to temporarily ignore events if necessary KEYFOLDER, DOWNLOADFOLDER, SYNCFOLDER, THUMBFOLDER, DIFFFOLDER, } return w, nil } func (fw *FileWatcher) Ignore() bool { fw.Enabled = false return fw.Enabled } func (fw *FileWatcher) Enable() bool { fw.Enabled = true return fw.Enabled } func (fw *FileWatcher) IsEnabled() bool { return fw.Enabled } // BeginWatcherRoutine kicks off the watcher. When the watcher noticies a file change, // certain actions will be taken in case of event and error // the routine will handle these whenever this occurs. // If certain functions need to be called then this will // need to be specified as part of the managers lambda functions // TODO: Should return an error func (fw *FileWatcher) BeginWatcherRoutine(ctx context.Context, wg *sync.WaitGroup, diffChannel chan database.DiffObject, onFileChanged func(string) (database.File, error)) { //seems a bit barking, but we can now cancel any diff that is occuring on a file when it fires again cancelFunctions := make(map[string]func()) for { select { // we have filtered already on the [Op]erations we want to listen for so no need to check here case event := <-fw.Watcher.Event: if !fw.IsEnabled() { fw.Info().Msgf("ignoring event and reenabling the watcher %s\r\n", event) fw.Enable() continue } fw.Info().Msgf("event fired ", event) //this is currently slow as it does a db lookup on the path. //TODO: On load (or whenever a file is added to the watcher, the db information for files being watched, could be cached in memory. This would be much faster) fileInfo, err := onFileChanged(event.Path) //could return the 'Event' object here syncFilePath := fileInfo.CurrentBase //uniqueName := fileInfo.Unique // fileID := fileInfo.ID //we need the hash of the current base, not the hash of the original file // fileHash := fileInfo.CurrentHash //hash needs to come from if err != nil { fw.Err(err).Msg("path was not returned to sync path") continue } //cancel the event if it indeed is running... if cancelFunctions[event.Path] != nil { cancelFunctions[event.Path]() delete(cancelFunctions, event.Path) } //context for the current event. Calling cancel will cancel the routines //to kill a context you must have access to the cancel function. // i could add the cancel function to a map of them // if you want to kill it you call on the correct cancel function // that will kill the context. Fine. // If however you want to kill it from another place.... // then you need a cancel channel, which inturn calls the equivelent cancel function.... ok // sounds about best i can do right now... // kind of bonkers right.... cancelContext, cancel := context.WithCancel(ctx) cancelFunctions[event.Path] = cancel // good idea to not use strings as keys directly as can conflict across namespaces // this needs to be sorted out -- too many things called an event.... // TODO: its totally bananas e := Event{ Name: event.Path, Progress: 0, Total: 100, } eventContext := context.WithValue(cancelContext, key(event.Path), e) if err := manageFileDiffing(eventContext, event.Path, syncFilePath, fw.DIFFFOLDER, true, diffChannel, wg); err != nil { // I don't think this can be reached... fw.Warn().Msgf("Error managing the diffing process %s", err) } case err := <-fw.Watcher.Error: fw.Err(err) case <-fw.Watcher.Closed: //fw.Notice("radovskyb closed") return } } }