Initial QSfera import
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Pablo
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package inotifywaitgo
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// CheckDependencies verifies if inotifywait is installed.
|
||||
func checkDependencies() (bool, error) {
|
||||
path, err := exec.LookPath("inotifywait")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if path != "" {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package inotifywaitgo
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GenerateShellCommands(s *Settings) ([]string, error) {
|
||||
if s.Options == nil {
|
||||
return nil, errors.New(OPT_NIL)
|
||||
}
|
||||
|
||||
if s.Dir == "" {
|
||||
return nil, errors.New(DIR_EMPTY)
|
||||
}
|
||||
|
||||
baseCmd := []string{
|
||||
"inotifywait",
|
||||
"-c", // switch to CSV output
|
||||
}
|
||||
|
||||
if s.Options.Monitor {
|
||||
baseCmd = append(baseCmd, "-m")
|
||||
}
|
||||
|
||||
if s.Options.Recursive {
|
||||
baseCmd = append(baseCmd, "-r")
|
||||
}
|
||||
|
||||
if len(s.Options.Events) > 0 {
|
||||
for _, event := range s.Options.Events {
|
||||
// if event not in VALID_EVENTS
|
||||
if !Contains(VALID_EVENTS, int(event)) {
|
||||
return nil, errors.New(INVALID_EVENT)
|
||||
}
|
||||
baseCmd = append(baseCmd, "-e", EVENT_MAP[int(event)])
|
||||
}
|
||||
}
|
||||
|
||||
baseCmd = append(baseCmd, s.Dir)
|
||||
|
||||
// Trim spaces on all elements
|
||||
var outCmd []string
|
||||
for _, v := range baseCmd {
|
||||
outCmd = append(outCmd, strings.TrimSpace(v))
|
||||
}
|
||||
|
||||
if s.Verbose {
|
||||
fmt.Println("Generated command:", strings.Join(outCmd, " "))
|
||||
}
|
||||
|
||||
return outCmd, nil
|
||||
}
|
||||
|
||||
// Contains checks if a slice contains an item
|
||||
func Contains[T comparable](slice []T, item T) bool {
|
||||
for _, v := range slice {
|
||||
if v == item {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
package inotifywaitgo
|
||||
|
||||
import "os/exec"
|
||||
|
||||
func killOthers() error {
|
||||
cmd := exec.Command("pkill", "inotifywait")
|
||||
return cmd.Run()
|
||||
}
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
package inotifywaitgo
|
||||
|
||||
import "log/slog"
|
||||
|
||||
type Settings struct {
|
||||
// Directory to watch
|
||||
Dir string
|
||||
// Channel to send the file name to
|
||||
FileEvents chan FileEvent
|
||||
// Channel to send errors to
|
||||
ErrorChan chan error
|
||||
// Options for inotifywait
|
||||
Options *Options
|
||||
// Kill other inotifywait processes
|
||||
KillOthers bool
|
||||
// verbose
|
||||
Verbose bool
|
||||
// Logger
|
||||
Log *slog.Logger
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// Watch the specified file or directory. If this option is not specified, inotifywait will watch the current working directory.
|
||||
Events []EVENT
|
||||
// Print the name of the file that triggered the event.
|
||||
Format string
|
||||
// Watch all subdirectories of any directories passed as arguments. Watches will be set up recursively to an unlimited depth. Symbolic links are not traversed. Newly created subdirectories will also be watched.
|
||||
Recursive bool
|
||||
// Set a time format string as accepted by strftime(3) for use with the `%T' conversion in the --format option.
|
||||
TimeFmt string
|
||||
// Instead of exiting after receiving a single event, execute indefinitely. The default behaviour is to exit after the first event occurs.
|
||||
Monitor bool
|
||||
}
|
||||
|
||||
const (
|
||||
// A watched file or a file within a watched directory was read from.
|
||||
EventAccess = "access"
|
||||
// A watched file or a file within a watched directory was written to.
|
||||
EventModify = "modify"
|
||||
// The metadata of a watched file or a file within a watched directory was modified. This includes timestamps, file permissions, extended attributes etc.
|
||||
EventAttrib = "attrib"
|
||||
// A watched file or a file within a watched directory was closed, after being opened in writable mode. This does not necessarily imply the file was written to.
|
||||
EventCloseWrite = "close_write"
|
||||
// A watched file or a file within a watched directory was closed, after being opened in read-only mode.
|
||||
EventCloseNowrite = "close_nowrite"
|
||||
// A watched file or a file within a watched directory was closed, regardless of how it was opened. Note that this is actually implemented simply by listening for both close_write and close_nowrite, hence all close events received will be output as one of these, not CLOSE.
|
||||
EventClose = "close"
|
||||
// A watched file or a file within a watched directory was opened.
|
||||
EventOpen = "open"
|
||||
// A watched file or a file within a watched directory was moved to the watched directory.
|
||||
EventMovedTo = "moved_to"
|
||||
// A watched file or a file within a watched directory was moved from the watched directory.
|
||||
EventMovedFrom = "moved_from"
|
||||
// A watched file or a file within a watched directory was moved to or from the watched directory. This is equivalent to listening for both moved_from and moved_to.
|
||||
EventMove = "move"
|
||||
// A watched file or directory was moved. After this event, the file or directory is no longer being watched.
|
||||
EventMoveSelf = "move_self"
|
||||
// A file or directory was created within a watched directory.
|
||||
EventCreate = "create"
|
||||
// A watched file or a file within a watched directory was deleted.
|
||||
EventDelete = "delete"
|
||||
// A watched file or directory was deleted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened for.
|
||||
EventDeleteSelf = "delete_self"
|
||||
// The filesystem on which a watched file or directory resides was unmounted. After this event the file or directory is no longer being watched. Note that this event can occur even if it is not explicitly being listened to.
|
||||
EventUnmount = "unmount"
|
||||
|
||||
// The subject of this event is a directory
|
||||
FlagIsdir = "ISDIR"
|
||||
)
|
||||
|
||||
type EVENT int
|
||||
|
||||
const (
|
||||
ACCESS = iota + 1000
|
||||
MODIFY
|
||||
ATTRIB
|
||||
CLOSE_WRITE
|
||||
CLOSE_NOWRITE
|
||||
CLOSE
|
||||
OPEN
|
||||
MOVED_TO
|
||||
MOVED_FROM
|
||||
MOVE
|
||||
MOVE_SELF
|
||||
CREATE
|
||||
DELETE
|
||||
DELETE_SELF
|
||||
UNMOUNT
|
||||
)
|
||||
|
||||
type FileEvent struct {
|
||||
Filename string
|
||||
Events []EVENT
|
||||
IsDir bool
|
||||
}
|
||||
|
||||
var EVENT_MAP = map[int]string{
|
||||
ACCESS: EventAccess,
|
||||
MODIFY: EventModify,
|
||||
ATTRIB: EventAttrib,
|
||||
CLOSE_WRITE: EventCloseWrite,
|
||||
CLOSE_NOWRITE: EventCloseNowrite,
|
||||
CLOSE: EventClose,
|
||||
OPEN: EventOpen,
|
||||
MOVED_TO: EventMovedTo,
|
||||
MOVED_FROM: EventMovedFrom,
|
||||
MOVE: EventMove,
|
||||
MOVE_SELF: EventMoveSelf,
|
||||
CREATE: EventCreate,
|
||||
DELETE: EventDelete,
|
||||
DELETE_SELF: EventDeleteSelf,
|
||||
UNMOUNT: EventUnmount,
|
||||
}
|
||||
|
||||
var EVENT_MAP_REVERSE = map[string]int{
|
||||
EventAccess: ACCESS,
|
||||
EventModify: MODIFY,
|
||||
EventAttrib: ATTRIB,
|
||||
EventCloseWrite: CLOSE_WRITE,
|
||||
EventCloseNowrite: CLOSE_NOWRITE,
|
||||
EventClose: CLOSE,
|
||||
EventOpen: OPEN,
|
||||
EventMovedTo: MOVED_TO,
|
||||
EventMovedFrom: MOVED_FROM,
|
||||
EventMove: MOVE,
|
||||
EventMoveSelf: MOVE_SELF,
|
||||
EventCreate: CREATE,
|
||||
EventDelete: DELETE,
|
||||
EventDeleteSelf: DELETE_SELF,
|
||||
EventUnmount: UNMOUNT,
|
||||
}
|
||||
|
||||
var VALID_EVENTS = []int{
|
||||
ACCESS,
|
||||
MODIFY,
|
||||
ATTRIB,
|
||||
CLOSE_WRITE,
|
||||
CLOSE_NOWRITE,
|
||||
CLOSE,
|
||||
OPEN,
|
||||
MOVED_TO,
|
||||
MOVED_FROM,
|
||||
MOVE,
|
||||
MOVE_SELF,
|
||||
CREATE,
|
||||
DELETE,
|
||||
DELETE_SELF,
|
||||
UNMOUNT,
|
||||
}
|
||||
|
||||
/* ERRORS */
|
||||
const (
|
||||
NOT_INSTALLED = "inotifywait is not installed"
|
||||
OPT_NIL = "optionsInotify is nil"
|
||||
DIR_EMPTY = "directory is empty"
|
||||
INVALID_EVENT = "invalid event"
|
||||
INVALID_OUTPUT = "invalid output"
|
||||
DIR_NOT_EXISTS = "directory does not exists"
|
||||
)
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
package inotifywaitgo
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/csv"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WatchPath starts watching a path for new files and returns the file name (abspath) when a new file is finished writing.
|
||||
func WatchPath(s *Settings) {
|
||||
// Check if inotifywait is installed
|
||||
if ok, err := checkDependencies(); !ok || err != nil {
|
||||
s.ErrorChan <- errors.New(NOT_INSTALLED)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the directory exists
|
||||
if _, err := os.Stat(s.Dir); os.IsNotExist(err) {
|
||||
s.ErrorChan <- errors.New(DIR_NOT_EXISTS)
|
||||
return
|
||||
}
|
||||
|
||||
// Stop any existing inotifywait processes
|
||||
if s.KillOthers {
|
||||
killOthers()
|
||||
}
|
||||
|
||||
// Generate shell command
|
||||
cmdString, err := GenerateShellCommands(s)
|
||||
if err != nil {
|
||||
s.ErrorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
// Start inotifywait in the input directory and watch for close_write events
|
||||
cmd := exec.Command(cmdString[0], cmdString[1:]...)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
s.ErrorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
s.ErrorChan <- err
|
||||
return
|
||||
}
|
||||
|
||||
logger := s.Log
|
||||
if logger == nil {
|
||||
logger = slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
}
|
||||
|
||||
// Read the output of inotifywait and split it into lines
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
logger.Debug(line)
|
||||
|
||||
parts, err := parseLine(line)
|
||||
if err != nil || len(parts) < 2 {
|
||||
s.ErrorChan <- errors.New(INVALID_OUTPUT)
|
||||
continue
|
||||
}
|
||||
|
||||
prefix, file := parts[0], parts[2]
|
||||
eventStrs := strings.Split(parts[1], ",")
|
||||
|
||||
if s.Verbose {
|
||||
for _, eventStr := range eventStrs {
|
||||
logger.Debug("eventStr: <%s>, <%s>", eventStr, line)
|
||||
}
|
||||
}
|
||||
|
||||
events, isDir := parseEvents(eventStrs, line, s)
|
||||
if events == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
event := FileEvent{
|
||||
Filename: prefix + file,
|
||||
Events: events,
|
||||
IsDir: isDir,
|
||||
}
|
||||
|
||||
// Send the file name to the channel
|
||||
s.FileEvents <- event
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
s.ErrorChan <- err
|
||||
}
|
||||
}
|
||||
|
||||
// parseLine parses a line of inotifywait output.
|
||||
func parseLine(line string) ([]string, error) {
|
||||
r := csv.NewReader(strings.NewReader(line))
|
||||
return r.Read()
|
||||
}
|
||||
|
||||
// parseEvents parses event strings into EVENT types.
|
||||
func parseEvents(eventStrs []string, line string, s *Settings) ([]EVENT, bool) {
|
||||
var events []EVENT
|
||||
isDir := false
|
||||
|
||||
for _, eventStr := range eventStrs {
|
||||
if eventStr == FlagIsdir {
|
||||
isDir = true
|
||||
continue
|
||||
}
|
||||
|
||||
eventStr = strings.ToLower(eventStr)
|
||||
event, ok := EVENT_MAP_REVERSE[eventStr]
|
||||
if !ok {
|
||||
s.ErrorChan <- fmt.Errorf("invalid eventStr: <%s>, <%s>", eventStr, line)
|
||||
return nil, false
|
||||
}
|
||||
events = append(events, EVENT(event))
|
||||
}
|
||||
|
||||
return events, isDir
|
||||
}
|
||||
Reference in New Issue
Block a user