Skip to content

Commit

Permalink
feat: create websocket connection to watch files
Browse files Browse the repository at this point in the history
  • Loading branch information
buildtheui committed Dec 15, 2023
1 parent 200f1fd commit 80c0117
Show file tree
Hide file tree
Showing 10 changed files with 263 additions and 8 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
PORT=3000
PORT=3000
TRANSFER_FOLDER=./files
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,8 @@ DropMyFile
# Go workspace file
go.work

files
# File folder
files

# system
.DS_store
9 changes: 7 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ go 1.21.5

require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/fasthttp/websocket v1.5.7 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gofiber/contrib/websocket v1.3.0 // indirect
github.com/gofiber/fiber/v2 v2.51.0 // indirect
github.com/gofiber/template v1.8.2 // indirect
github.com/gofiber/template/html/v2 v2.0.5 // indirect
github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/klauspost/compress v1.17.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mdp/qrterminal v1.0.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
rsc.io/qr v0.2.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/fasthttp/websocket v1.5.7 h1:0a6o2OfeATvtGgoMKleURhLT6JqWPg7fYfWnH4KHau4=
github.com/fasthttp/websocket v1.5.7/go.mod h1:bC4fxSono9czeXHQUVKxsC0sNjbm7lPJR04GDFqClfU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gofiber/contrib/websocket v1.3.0 h1:XADFAGorer1VJ1bqC4UkCjqS37kwRTV0415+050NrMk=
github.com/gofiber/contrib/websocket v1.3.0/go.mod h1:xguaOzn2ZZ759LavtosEP+rcxIgBEE/rdumPINhR+Xo=
github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ=
github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U=
github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk=
Expand All @@ -14,6 +20,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.3 h1:qkRjuerhUU1EmXLYGkSH6EZL+vPSxIrYjLNAK4slzwA=
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
Expand All @@ -25,12 +33,18 @@ github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c=
github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
Expand Down
87 changes: 86 additions & 1 deletion pkg/api/api.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
package api

import (
"encoding/json"
"fmt"
"log"
"os"

folderwatch "github.com/buildtheui/DropMyFile/pkg/folder_watch"
"github.com/buildtheui/DropMyFile/pkg/global"
"github.com/buildtheui/DropMyFile/pkg/utils"
"github.com/gofiber/contrib/websocket"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/template/html/v2"
)

type client struct{}

var App *fiber.App
var clients = make(map[*websocket.Conn]client)
var registerConn = make(chan *websocket.Conn)
var unRegisterConn = make(chan *websocket.Conn)
var broadcast = make(chan []string)

func setupRoutes() {
// Load extra needed files for the views like css or js
Expand All @@ -19,6 +30,16 @@ func setupRoutes() {
App.Get("/", func(c *fiber.Ctx) error {
return c.Render("index", fiber.Map{ "Session": global.GSession })
})

App.Use("/ws", func(c *fiber.Ctx) error {
// IsWebSocketUpgrade returns true if the client
// requested upgrade to the WebSocket protocol.
if websocket.IsWebSocketUpgrade(c) {
c.Locals("allowed", true)
return c.Next()
}
return fiber.ErrUpgradeRequired
})
}

func setUpApis() {
Expand All @@ -33,7 +54,7 @@ func setUpApis() {
files := form.File["files"]

for _, file := range files {
err := c.SaveFile(file, fmt.Sprintf("./files/%s", utils.RenameFileToUnique(file.Filename)))
err := c.SaveFile(file, fmt.Sprintf("%s/%s", os.Getenv("TRANSFER_FOLDER"), utils.RenameFileToUnique(file.Filename)))

if err != nil {
return err
Expand All @@ -44,6 +65,69 @@ func setUpApis() {
})
}

func broadcastFileChanges() {
for {
select {
case connection := <- registerConn:
clients[connection] = client{}

case <-broadcast:
files, _ := folderwatch.GetTransferFilesInfo()


jsonData, err := json.Marshal(files)
if err != nil {
log.Println("Error listing files:", err)
continue
}

// Send the file change information to all clients
for connection := range clients {
if err := connection.WriteMessage(websocket.TextMessage, jsonData); err != nil {
log.Println("Send files to clients error:", err)

unRegisterConn <- connection
connection.WriteMessage(websocket.CloseMessage, []byte{})
connection.Close()
}
}

case connection := <- unRegisterConn:
delete(clients, connection)
}
}
}


func setUpWebSockets() {

go folderwatch.WatchFileChanges(broadcast)
go broadcastFileChanges()

App.Get("/ws/files", websocket.New(func(c *websocket.Conn) {
// When the function returns, unregister the client and close the connection
defer func() {
unRegisterConn <- c
c.Close()
}()

// register new client
registerConn <- c

for {
_, _, err := c.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Println("read error:", err)
}

return // Calls the deferred function, i.e. closes the connection on error
}
}

}));
}

func RouterInit() *fiber.App {
// Load templates
var engine = html.New("./views", ".html")
Expand All @@ -59,6 +143,7 @@ func RouterInit() *fiber.App {
// Call setupRoutes to set up your routes
setupRoutes()
setUpApis()
setUpWebSockets()

return App;
}
84 changes: 84 additions & 0 deletions pkg/folder_watch/folder_watch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package folderwatch

import (
"fmt"
"log"
"os"

"github.com/buildtheui/DropMyFile/pkg/models"
"github.com/buildtheui/DropMyFile/pkg/network"
"github.com/fsnotify/fsnotify"
)

func WatchFileChanges(folderChange chan<- []string) {
// Specify the folder to watch
folderPath := os.Getenv("TRANSFER_FOLDER")

// Create new watcher.
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()

// Start listening for events.
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
// Notify folder changed
folderChange <- []string{event.Name}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()

// Watches the transfer folder for changes
err = watcher.Add(folderPath)

if err != nil {
log.Fatal(err)
}

// Wait forever to keep watching
select {}
}

func GetTransferFilesInfo() ([]models.FileInfo, error) {
// Specify the folder to watch
folderPath := os.Getenv("TRANSFER_FOLDER")

files, err := os.ReadDir(folderPath)
if err != nil {
fmt.Println("Error reading directory:", err)
return nil, err
}

// Create a slice to store FileInfo structs
var fileInfos []models.FileInfo

// Iterate over files and populate FileInfo structs
for _, file := range files {
if !file.IsDir() {
downloadLink := network.GetServerAddr(fmt.Sprintf("/api/v1/download/%s", file.Name()))

// Create a FileInfo struct for the current file
fileInfo := models.FileInfo{
FileName: file.Name(),
DownloadLink: downloadLink,
}

// Append FileInfo to the slice
fileInfos = append(fileInfos, fileInfo)
}
}

return fileInfos, nil
}
7 changes: 7 additions & 0 deletions pkg/models/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package models

// FileInfo represents information about a file, including its name and download link.
type FileInfo struct {
FileName string `json:"fileName"`
DownloadLink string `json:"downloadLink"`
}
17 changes: 15 additions & 2 deletions pkg/network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import (
"github.com/mdp/qrterminal"
)

var myIp string

func PrintLanServerIpQr() {
myIp, _ := getLocalIp()
serverAddr := fmt.Sprintf("http://%s:%s/?s=%s", myIp, GetServerPort(), global.GSession)
serverAddr := GetServerAddr("/")
qrterminal.Generate(serverAddr, qrterminal.L, os.Stdout)
fmt.Println("Or go to: " + serverAddr)
}
Expand All @@ -39,4 +40,16 @@ func GetServerPort() string {
}

return port
}

func GetServerAddr(path string) string {
if myIp == "" {
var err error
myIp, err = getLocalIp()

if err != nil {
log.Fatal("LAN address could not be found")
}
}
return fmt.Sprintf("http://%s:%s%s?s=%s", myIp, GetServerPort(), path, global.GSession)
}
42 changes: 42 additions & 0 deletions views/assets/upload.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,54 @@
"use strict";

function initWebSocket(session) {
const socket = new WebSocket(
"ws://" + window.location.host + "/ws/files?s=" + session
);

// Connection opened
socket.addEventListener("open", (event) => {
console.log("WebSocket connection opened:", event);
});

// Listen for messages
socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("WebSocket message received:", data);

// Handle the received data as needed
// Example: Update the UI with the received data
updateUI(data);
});

// Connection closed
socket.addEventListener("close", (event) => {
console.log("WebSocket connection closed:", event);
});

// Connection error
socket.addEventListener("error", (event) => {
console.error("WebSocket connection error:", event);
});

// Example function to update the UI with received data
function updateUI(data) {
// Implement your UI update logic here
// Example: Display the received data in the console
console.log("UI updated with data:", data);
}
}

document.addEventListener("alpine:init", () => {
Alpine.data("global", () => ({
isLoading: false,
toastContent: { isOpen: false },
files: [],
progress: 0,
session: "",
initData(session) {
initWebSocket(session);
return session;
},
toggleToast(isOpen, data) {
this.toastContent = {
isOpen,
Expand Down
Loading

0 comments on commit 80c0117

Please sign in to comment.