package main import ( "flag" "fmt" "io" "log" "os" "os/signal" "path/filepath" "syscall" "time" "github.com/Sudo-Ivan/reticulum-go/internal/config" "github.com/Sudo-Ivan/reticulum-go/pkg/common" "github.com/Sudo-Ivan/reticulum-go/pkg/destination" "github.com/Sudo-Ivan/reticulum-go/pkg/identity" "github.com/Sudo-Ivan/reticulum-go/pkg/link" "github.com/Sudo-Ivan/reticulum-go/pkg/packet" "github.com/Sudo-Ivan/reticulum-go/pkg/resource" "github.com/Sudo-Ivan/reticulum-go/pkg/transport" ) const ( APP_NAME = "example_utilities" APP_ASPECT = "filetransfer" ) var ( configPath = flag.String("config", "", "Path to config file") servePath = flag.String("serve", "", "Directory to serve files from") ) type FileServer struct { config *common.ReticulumConfig transport *transport.Transport interfaces []common.NetworkInterface identity *identity.Identity servePath string } func NewFileServer(cfg *common.ReticulumConfig, servePath string) (*FileServer, error) { if cfg == nil { var err error cfg, err = config.InitConfig() if err != nil { return nil, fmt.Errorf("failed to initialize config: %v", err) } } t, err := transport.NewTransport(cfg) if err != nil { return nil, fmt.Errorf("failed to initialize transport: %v", err) } id, err := identity.New() if err != nil { return nil, fmt.Errorf("failed to create identity: %v", err) } return &FileServer{ config: cfg, transport: t, interfaces: make([]common.NetworkInterface, 0), identity: id, servePath: servePath, }, nil } func (s *FileServer) OnLinkEstablished(l *link.Link) { s.handleLinkEstablished(l) } func (s *FileServer) Start() error { dest, err := destination.New( s.identity, destination.OUT, destination.SINGLE, APP_NAME, APP_ASPECT, ) if err != nil { return fmt.Errorf("failed to create destination: %v", err) } callback := func(l interface{}) { if link, ok := l.(*link.Link); ok { s.OnLinkEstablished(link) } } dest.SetLinkEstablishedCallback(callback) log.Printf("File server started. Server hash: %s", s.identity.Hex()) log.Printf("Serving directory: %s", s.servePath) return nil } func (s *FileServer) handleLinkEstablished(l *link.Link) { log.Printf("Client connected") l.SetPacketCallback(func(data []byte, p *packet.Packet) { s.handlePacket(data, l) }) l.SetResourceCallback(func(r interface{}) bool { if res, ok := r.(*resource.Resource); ok { return s.handleResource(res) } return false }) } func (s *FileServer) handlePacket(data []byte, l *link.Link) { if string(data) == "LIST" { files, err := s.getFileList() if err != nil { log.Printf("Error getting file list: %v", err) l.Teardown() return } if err := l.SendPacket(files); err != nil { log.Printf("Error sending file list: %v", err) l.Teardown() } } } func (s *FileServer) handleResource(r *resource.Resource) bool { filename := filepath.Join(s.servePath, r.GetName()) file, err := os.Create(filename) if err != nil { log.Printf("Failed to create file: %v", err) return false } defer file.Close() written, err := io.Copy(file, r) if err != nil { log.Printf("Failed to write file: %v", err) return false } log.Printf("Received file: %s (%d bytes)", filename, written) return true } func (s *FileServer) getFileList() ([]byte, error) { files, err := os.ReadDir(s.servePath) if err != nil { return nil, err } var fileList []string for _, file := range files { if !file.IsDir() { fileList = append(fileList, file.Name()) } } return []byte(fmt.Sprintf("%v", fileList)), nil } func main() { flag.Parse() if *servePath == "" { log.Fatal("Please specify a directory to serve with -serve") } var cfg *common.ReticulumConfig var err error if *configPath == "" { cfg, err = config.InitConfig() } else { cfg, err = config.LoadConfig(*configPath) } if err != nil { log.Fatalf("Failed to load config: %v", err) } server, err := NewFileServer(cfg, *servePath) if err != nil { log.Fatalf("Failed to create server: %v", err) } if err := server.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } // Start watching the directory for changes go server.watchDirectory() sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) <-sigChan } func (s *FileServer) watchDirectory() { for { time.Sleep(1 * time.Second) files, err := os.ReadDir(s.servePath) if err != nil { log.Printf("Error reading directory: %v", err) continue } for _, file := range files { if !file.IsDir() { // Try to send file to connected peers filePath := filepath.Join(s.servePath, file.Name()) if err := s.sendFile(filePath); err != nil { log.Printf("Error sending file %s: %v", file.Name(), err) } } } } } func (s *FileServer) sendFile(filePath string) error { file, err := os.Open(filePath) if err != nil { return fmt.Errorf("failed to open file: %v", err) } defer file.Close() // Create a destination for the file transfer dest, err := destination.New( s.identity, destination.OUT, destination.SINGLE, APP_NAME, APP_ASPECT, ) if err != nil { return fmt.Errorf("failed to create destination: %v", err) } // Set up link for file transfer callback := func(l interface{}) { if link, ok := l.(*link.Link); ok { // Create a new resource with auto-compression enabled res, err := resource.New(file, true) if err != nil { log.Printf("Error creating resource: %v", err) return } // The filename is automatically set from the file handle // in resource.New when using an io.ReadWriteSeeker // Send the resource through the link if err := link.SendResource(res); err != nil { log.Printf("Error sending resource: %v", err) return } log.Printf("File %s sent successfully", filepath.Base(filePath)) } } dest.SetLinkEstablishedCallback(callback) return nil }