Skip to content

Commit

Permalink
Using winrt api to register sync root
Browse files Browse the repository at this point in the history
  • Loading branch information
balazsgrill committed Aug 17, 2024
1 parent 6fb3be7 commit b65069d
Show file tree
Hide file tree
Showing 19 changed files with 389 additions and 285 deletions.
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,34 @@
# PotatoDrive

Windows user-space locally-cached virtual file system client. Based on [Projected File System](https://learn.microsoft.com/en-us/windows/win32/projfs/projected-file-system) service available on Windows 10+ and works similar to OneDrive, using the local directory as a cache of the remote state.
## What?

:construction: PotatoDrive is in early development phase, don't trust it with data you're not willing to lose. Keep your backups safe (as always)!

Features:
Windows user-space locally-cached virtual file system client for windows 10+, capable of mounting remote folders as local. Can be used as convenient way to work with files stored in cloud storage or on a server, or as a way to backup and extend local storage.

* Windows 10+
* Supports standard S3 (AWS, BackBlaze, Minio, etc..) as backend without additional server
* Supports standard cloud or server storage backends without additional software
* S3 (AWS, BackBlaze, Minio, etc..)
* SFTP (SSH)
* Files are cached locally
* Multiple folder bindings on a single machine

## How?

Translates a [filesystem abstraction](https://github.com/spf13/afero) to a user space file system API provided by windows that can be either [Projected File System](https://learn.microsoft.com/en-us/windows/win32/projfs/projected-file-system) or [Cloud Files API](https://learn.microsoft.com/en-us/windows/win32/cfApi/cloud-files-api-portal).

## Why?

This software support two use cases:

* Extend the storage capacity of a local drive with a replicated cloud storage backend
* Example 1: directly mount [AWS](https://aws.amazon.com/s3/) or [BackBlaze](https://www.backblaze.com/docs/cloud-storage-s3-compatible-api) buckets as local folders
* Example 2: mount a self-hosted (optionally replicated) [Minio](https://min.io/) server
* Edit files stored on your SFTP server as if they were local files

## Installation

Installer is not (yet) available, latest binaries can be downloaded as [artifacts from successful builds](https://github.com/balazsgrill/potatodrive/actions).

To work, Projected File System service needs to be [enabled](https://learn.microsoft.com/en-us/windows/win32/projfs/enabling-windows-projected-file-system) (disabled by default):
If used, Projected File System service needs to be [enabled](https://learn.microsoft.com/en-us/windows/win32/projfs/enabling-windows-projected-file-system) (disabled by default):

```PowerShell
Enable-WindowsOptionalFeature -Online -FeatureName Client-ProjFS -NoRestart
Expand All @@ -27,10 +40,4 @@ Configuration is stored in Windows Registry, see [example.reg](example/potatodri

## Running

Once configured,

# Development

* [afero](https://github.com/spf13/afero) is used as backend interface, meaning that any

Windows user-space virtual file system binding to [afero](https://github.com/spf13/afero) file systems backend. Makes it possible to implement virtual file systems for windows backed by any kind of user-space implementation of virtual file system.
Once configured, just run the application. Logs are written to `%LOCALAPPDATA%\PotatoDrive`.
15 changes: 10 additions & 5 deletions bindings/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"syscall"
"time"

"github.com/rs/zerolog/log"
"github.com/rs/zerolog"

"github.com/balazsgrill/potatodrive/bindings/s3"
"github.com/balazsgrill/potatodrive/bindings/sftp"
Expand Down Expand Up @@ -116,13 +116,17 @@ func (f closerFunc) Close() error {
return f()
}

func BindVirtualizationInstance(localpath string, remotefs afero.Fs) (io.Closer, error) {
func BindVirtualizationInstance(id string, localpath string, remotefs afero.Fs, logger zerolog.Logger, statecallback func(error)) (io.Closer, error) {
var closer win.Virtualization
var err error
if UseCFAPI {
closer, err = cfapi.StartProjecting(localpath, remotefs)
err = cfapi.RegisterRootPath(id, localpath)
if err != nil {
return nil, err
}
closer, err = cfapi.StartProjecting(localpath, remotefs, logger)
} else {
closer, err = prjfs.StartProjecting(localpath, remotefs)
closer, err = prjfs.StartProjecting(localpath, remotefs, logger)
}
if err != nil {
return nil, err
Expand All @@ -133,8 +137,9 @@ func BindVirtualizationInstance(localpath string, remotefs afero.Fs) (io.Closer,
for range t.C {
err = closer.PerformSynchronization()
if err != nil {
log.Print(err)
logger.Err(err).Send()
}
statecallback(err)
}
}()

Expand Down
96 changes: 0 additions & 96 deletions cmd/config/main.go

This file was deleted.

File renamed without changes.
File renamed without changes.
94 changes: 14 additions & 80 deletions cmd/main/main.go
Original file line number Diff line number Diff line change
@@ -1,98 +1,32 @@
package main

import (
"io"
"os"
"path/filepath"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"

"github.com/balazsgrill/potatodrive/bindings"
"golang.org/x/sys/windows/registry"
)

func initLogger() (zerolog.Logger, io.Closer) {
cachedir, err := os.UserCacheDir()
if err != nil {
panic(err)
}
logfolder := filepath.Join(cachedir, "PotatoDrive")
err = os.MkdirAll(logfolder, 0777)
if err != nil {
panic(err)
}

logfile := "potatodrive.log"
logf, err := os.OpenFile(filepath.Join(logfolder, logfile), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
panic(err)
}
return log.Output(logf).With().Timestamp().Logger(), logf
}

func startInstance(parentkey registry.Key, keyname string) (io.Closer, error) {
key, err := registry.OpenKey(parentkey, keyname, registry.QUERY_VALUE)
if err != nil {
log.Printf("Open key: %v", err)
return nil, err
}

var basec bindings.BaseConfig
err = bindings.ReadConfigFromRegistry(key, &basec)
if err != nil {
log.Printf("Get base config: %v", err)
return nil, err
}
config := bindings.CreateConfigByType(basec.Type)
bindings.ReadConfigFromRegistry(key, config)
err = config.Validate()
if err != nil {
log.Printf("Validate config: %v", err)
return nil, err
}
fs, err := config.ToFileSystem()
if err != nil {
log.Printf("Create file system: %v", err)
return nil, err
}

log.Printf("Starting %s on %s", keyname, basec.LocalPath)
c, err := bindings.BindVirtualizationInstance(basec.LocalPath, fs)
if err != nil {
log.Print(err)
}
log.Printf("%s started", keyname)
return c, nil
}

func main() {
var logf io.Closer
log.Logger, logf = initLogger()
defer logf.Close()

parentkey, err := registry.OpenKey(registry.LOCAL_MACHINE, "SOFTWARE\\PotatoDrive", registry.QUERY_VALUE|registry.READ)
if err != nil {
panic(err)
}

keys, err := parentkey.ReadSubKeyNames(0)
mgr, err := New()
if err != nil {
panic(err)
log.Print(err)
return
}
ui := createUI(mgr.Logger)
defer ui.ni.Dispose()

var instances []io.Closer

keys, _ := mgr.InstanceList()
for _, keyname := range keys {
c, err := startInstance(parentkey, keyname)
err := mgr.StartInstance(keyname, ui.Logger, func(err error) {
if err != nil {
ui.Logger.Err(err).Msgf("%s is offline %v", keyname, err)
}
})
if err != nil {
log.Printf("Start instance: %v", err)
} else {
instances = append(instances, c)
ui.Logger.Err(err).Msgf("Failed to start %s", keyname)
}
}

bindings.CloseOnSigTerm(instances...)

select {}
go bindings.CloseOnSigTerm(mgr)
ui.Run()
}
Loading

0 comments on commit b65069d

Please sign in to comment.