Skip to content

Commit

Permalink
Handle unstable networks and keep the connections alive until finishi…
Browse files Browse the repository at this point in the history
…ng the download
  • Loading branch information
aleskandro committed Aug 30, 2024
1 parent 33b4b7d commit a995b59
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 34 deletions.
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func main() {
controller := pkg.NewNetworkConnectionReconciler(config)
ctx := SetupSignalHandler()
if *sync {
_, _, err = controller.SyncNow()
_, _, err = controller.SyncNow(ctx)
if err != nil {
return
}
Expand Down
67 changes: 48 additions & 19 deletions pkg/contoller.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"os"
"path"
"sync"
"time"

"github.com/godbus/dbus/v5"
Expand All @@ -18,7 +19,7 @@ import (
type NetworkConnectionReconciler struct {
conn *dbus.Conn
ch chan *dbus.Signal
syncer *Syncer
config *Config
}

var networkConnectionFailedErr = fmt.Errorf("network connection failed")
Expand All @@ -38,7 +39,7 @@ func NewNetworkConnectionReconciler(config *Config) *NetworkConnectionReconciler
return &NetworkConnectionReconciler{
conn: conn,
ch: ch,
syncer: NewSyncer(config),
config: config,
}
}

Expand All @@ -50,6 +51,9 @@ func (n *NetworkConnectionReconciler) Run(ctx context.Context) {
//nolint:errcheck
n.conn.Close()
}()
var cancel context.CancelFunc
var childCtx context.Context
var wg = sync.WaitGroup{}
for {
fmt.Println("Listening for network connection signals from Nickel...")
select {
Expand All @@ -71,16 +75,31 @@ func (n *NetworkConnectionReconciler) Run(ctx context.Context) {
log.Println("Received unexpected signal", signal.Name)
continue
}
err := n.handleWmNetworkConnected()
if err != nil {
log.Println("Failed to handle network connected signal", err)
if cancel != nil {
cancel()
}
wg.Wait()
childCtx, cancel = context.WithCancel(ctx)
wg.Add(1)
go func() {
defer func() {
wg.Done()
if childCtx.Err() == nil {
cancel()
cancel = nil
}
}()
err := n.handleWmNetworkConnected(childCtx)
if err != nil {
log.Println("Failed to handle network connected signal", err)
}
}()
}
}
}

func (n *NetworkConnectionReconciler) handleWmNetworkConnected() error {
filesMap, nUpdatedFiles, err := n.SyncNow()
func (n *NetworkConnectionReconciler) handleWmNetworkConnected(ctx context.Context) error {
filesMap, nUpdatedFiles, err := n.SyncNow(ctx)
if err != nil {
log.Println("Failed to sync", err)
if errors.Is(err, networkConnectionFailedErr) {
Expand All @@ -93,7 +112,7 @@ func (n *NetworkConnectionReconciler) handleWmNetworkConnected() error {
n.notifyNickel(fmt.Sprintf("Synced %d files:\n%s", nUpdatedFiles, generateFilesString(filesMap)))
}
log.Println("Sync successful")
if n.syncer.config.AutoUpdate && n.UpdateNow() {
if n.config.AutoUpdate && n.UpdateNow() {
log.Println("Auto update successful")
n.notifyNickel("An update for Nextcloud-Kobo is available")
os.Exit(0) // Exit to restart the application
Expand All @@ -105,14 +124,14 @@ func (n *NetworkConnectionReconciler) UpdateNow() bool {
// Check the latest version on GitHub
cli := github.NewClient(nil)
release, _, err := cli.Repositories.GetLatestRelease(context.Background(),
n.syncer.config.RepoOwner, n.syncer.config.RepoName)
n.config.RepoOwner, n.config.RepoName)
// If we can't get the latest release, don't update
if err != nil {
log.Println("Failed to get latest release", err)
return false
}
// get the latest updated version stored in the config
version, err := os.ReadFile(path.Join(n.syncer.config.configPath, "version.txt"))
version, err := os.ReadFile(path.Join(n.config.configPath, "version.txt"))
if err != nil && !os.IsNotExist(err) {
log.Println("Failed to read version file", err)
return false
Expand All @@ -137,7 +156,7 @@ func (n *NetworkConnectionReconciler) UpdateNow() bool {
defer resp.Body.Close()

// Save the latest release to a file
file, err := os.Create(path.Join(n.syncer.config.configPath, "nextcloud-kobo.tar.gz"))
file, err := os.Create(path.Join(n.config.configPath, "nextcloud-kobo.tar.gz"))
if err != nil {
log.Println("Failed to create release file", err)
return false
Expand All @@ -151,7 +170,7 @@ func (n *NetworkConnectionReconciler) UpdateNow() bool {
return false
}
// Write the latest release to a file
versionFile, err := os.Create(path.Join(n.syncer.config.configPath, "version.txt"))
versionFile, err := os.Create(path.Join(n.config.configPath, "version.txt"))
if err != nil {
log.Println("Failed to create version file", err)
return false
Expand All @@ -164,15 +183,17 @@ func (n *NetworkConnectionReconciler) UpdateNow() bool {
return true
}

func (n *NetworkConnectionReconciler) SyncNow() (filesMap map[string][]string, nUpdatedFiles int, err error) {
if err = checkNetwork(); err != nil {
func (n *NetworkConnectionReconciler) SyncNow(ctx context.Context) (filesMap map[string][]string, nUpdatedFiles int, err error) {
checkNetworkCtx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
if err = checkNetwork(checkNetworkCtx); err != nil {
log.Println("Network connection failed", err)
return filesMap, 0, networkConnectionFailedErr
}

filesMap, err = n.syncer.RunSync()
n.notifyNickel("Syncing with Nextcloud...")
filesMap, err = n.runSync(ctx)
if err != nil {
return
log.Println("An error occurred during synchronization", err)
}
for _, files := range filesMap {
nUpdatedFiles += len(files)
Expand Down Expand Up @@ -206,14 +227,22 @@ func (n *NetworkConnectionReconciler) notifyNickel(message string) {
}
}

func checkNetwork() error {
func (n *NetworkConnectionReconciler) keepNetworkAlive() {
obj := n.conn.Object("com.github.shermp.nickeldbus", "/nickeldbus")
call := obj.Call("com.github.shermp.nickeldbus.wfmConnectWirelessSilently", 0)
if call.Err != nil {
log.Println("Failed to notify Nickel", call.Err)
}
}

func checkNetwork(ctx context.Context) error {
// Wait for the network to be fully connected
for i := 0; i < 10; i++ {
// Check if a web request to google is successful
client := &http.Client{
Timeout: 5 * time.Second,
}
req, err := http.NewRequest("GET", "http://www.google.com", nil)
req, err := http.NewRequestWithContext(ctx, "GET", "http://www.google.com", nil)
if err != nil {
log.Println("Fatal error", err)
return err
Expand Down
36 changes: 22 additions & 14 deletions pkg/syncer.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,29 @@
package pkg

import (
"context"
"fmt"
"log"
"os"
"path"
"time"

"github.com/studio-b12/gowebdav"
)

type Syncer struct {
config *Config
}

func NewSyncer(config *Config) *Syncer {
return &Syncer{config: config}
}

func (c *Syncer) RunSync() (updatedFiles map[string][]string, err error) {
func (n *NetworkConnectionReconciler) runSync(ctx context.Context) (updatedFiles map[string][]string, err error) {
updatedFiles = make(map[string][]string)
log.Println("Running sync")
for _, r := range c.config.Remotes {
log.Println("Syncing remote", r.String())
for _, r := range n.config.Remotes {
if err = ctx.Err(); err != nil {
log.Println("The context has been canceled. Interrupting...")
err = ctx.Err()
return
}
client := gowebdav.NewClient(r.remoteURL.String(), r.Username, r.Password)
updatedFiles[r.String()], err = c.syncFolder(client, r.RemoteFolder, r.LocalPath)
// 10 Mb/s * 4 min * 60 s/min * 1/8 B/b = 300 MB per file/book max with a 10 Mbps connection(?)
client.SetTimeout(time.Minute * 4)
updatedFiles[r.String()], err = n.syncFolder(client, ctx, r.RemoteFolder, r.LocalPath)
if err != nil {
log.Println("error syncing folder", r.String(), err)
return updatedFiles, fmt.Errorf("error syncing folder %s: %s", r.String(), err)
Expand All @@ -33,7 +33,7 @@ func (c *Syncer) RunSync() (updatedFiles map[string][]string, err error) {
return
}

func (c *Syncer) syncFolder(client *gowebdav.Client, remotePath, localPath string) (updatedFiles []string, err error) {
func (n *NetworkConnectionReconciler) syncFolder(client *gowebdav.Client, ctx context.Context, remotePath, localPath string) (updatedFiles []string, err error) {
var remoteFiles []os.FileInfo
updatedFiles = []string{}
remoteFiles, err = client.ReadDir(remotePath)
Expand All @@ -46,22 +46,29 @@ func (c *Syncer) syncFolder(client *gowebdav.Client, remotePath, localPath strin
}
localFileMap := make(map[string]string)
for _, file := range remoteFiles {
if ctx.Err() != nil {
log.Println("The context has been canceled. Interrupting...")
err = ctx.Err()
return
}
remoteFilePath := path.Join(remotePath, file.Name())
localFilePath := path.Join(localPath, file.Name())
log.Println("Checking file", remoteFilePath, localFilePath)
if file.IsDir() {
log.Println(remoteFilePath, "is a dir. Executing recursion...", localFilePath)
if ensureDirExists(localFilePath) != nil {
return
}
var updatedFilesRec []string
updatedFilesRec, err = c.syncFolder(client, remoteFilePath+"/", localFilePath)
updatedFilesRec, err = n.syncFolder(client, ctx, remoteFilePath+"/", localFilePath)
updatedFiles = append(updatedFiles, updatedFilesRec...)
if err != nil {
return
}
} else {
localFileMap[localFilePath] = localFilePath
if shouldDownloadFile(localFilePath, file.ModTime(), file.Size()) {
n.keepNetworkAlive()
log.Printf("Downloading file %s to %s\n", remoteFilePath, localFilePath)
var data []byte
data, err = client.Read(remoteFilePath)
Expand All @@ -75,6 +82,7 @@ func (c *Syncer) syncFolder(client *gowebdav.Client, remotePath, localPath strin
}
updatedFiles = append(updatedFiles, localFilePath)
log.Println("Downloaded file", localFilePath)
n.notifyNickel(fmt.Sprintf("Downloaded %s", remoteFilePath))
} else {
log.Println("Skipping file", remoteFilePath)
}
Expand Down

0 comments on commit a995b59

Please sign in to comment.