Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add drive rename option #394

Merged
merged 6 commits into from
Apr 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
- personal
- business
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4

- uses: actions/setup-go@v2
- uses: actions/setup-go@v5
with:
go-version: "1.18"
go-version: "1.19"

- name: Install apt dependencies
run: |
Expand All @@ -33,7 +33,7 @@ jobs:
libreoffice
sudo rm /usr/local/bin/aws # whyyy

- uses: actions/cache@v2
- uses: actions/cache@v4
with:
path: |
~/go/pkg/mod
Expand Down Expand Up @@ -63,12 +63,12 @@ jobs:
# cannot run systemd tests here because github actions runners don't have dbus setup +
# if CGO is on, the UI tests will take foreverrrrr
bash cgo-helper.sh
CGO_ENABLED=0 gotest -v -covermode=count -coverpkg=./ui/... -coverprofile=ui.coverage ./ui
gotest -v -covermode=count -coverpkg=./cmd/common -coverprofile=common.coverage ./cmd/common
gotest -v -covermode=count -coverpkg=./fs/... -coverprofile=quickxorhash.coverage ./fs/graph/quickxorhash
gotest -v -covermode=count -coverpkg=./fs/... -coverprofile=graph.coverage ./fs/graph
gotest -v -covermode=count -coverpkg=./fs/... -coverprofile=fs.coverage ./fs
go test -c -covermode=count -coverpkg=./fs/... ./fs/offline
CGO_ENABLED=0 gotest -v -tags=glib_2_64 -covermode=count -coverpkg=./ui/... -coverprofile=ui.coverage ./ui
gotest -v -tags=glib_2_64 -covermode=count -coverpkg=./cmd/common -coverprofile=common.coverage ./cmd/common
gotest -v -tags=glib_2_64 -covermode=count -coverpkg=./fs/... -coverprofile=quickxorhash.coverage ./fs/graph/quickxorhash
gotest -v -tags=glib_2_64 -covermode=count -coverpkg=./fs/... -coverprofile=graph.coverage ./fs/graph
gotest -v -tags=glib_2_64 -covermode=count -coverpkg=./fs/... -coverprofile=fs.coverage ./fs
go test -c -tags=glib_2_64 -covermode=count -coverpkg=./fs/... ./fs/offline
sudo unshare -n -S $(id -u) -G $(id -g) ./offline.test -test.v -test.coverprofile=offline.coverage

- name: Copy new auth tokens to S3
Expand All @@ -92,7 +92,7 @@ jobs:
if: always()

- name: Send test coverage to Coveralls
uses: coverallsapp/github-action@v1.1.2
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: coverage.lcov
Expand All @@ -108,7 +108,7 @@ jobs:
runs-on: ubuntu-20.04
steps:
- name: Coveralls finished
uses: coverallsapp/github-action@v1.1.2
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.github_token }}
parallel-finished: true
Expand Down
28 changes: 28 additions & 0 deletions cmd/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
package common

import (
"errors"
"fmt"
"io/ioutil"
"os"
"regexp"

"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
Expand Down Expand Up @@ -35,3 +39,27 @@ func StringToLevel(input string) zerolog.Level {
func LogLevels() []string {
return []string{"trace", "debug", "info", "warn", "error", "fatal"}
}

// TemplateXDGVolumeInfo returns
func TemplateXDGVolumeInfo(name string) string {
xdgVolumeInfo := fmt.Sprintf("[Volume Info]\nName=%s\n", name)
if _, err := os.Stat("/usr/share/icons/onedriver/onedriver.png"); err == nil {
xdgVolumeInfo += "IconFile=/usr/share/icons/onedriver/onedriver.png\n"
}
return xdgVolumeInfo
}

// GetXDGVolumeInfoName returns the name of the drive according to whatever the
// user has named it.
func GetXDGVolumeInfoName(path string) (string, error) {
contents, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
regex := regexp.MustCompile("Name=(.*)")
name := regex.FindString(string(contents))
if len(name) < 5 {
return "", errors.New("could not find \"Name=\" key")
}
return name[5:], nil
}
20 changes: 20 additions & 0 deletions cmd/common/common_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package common

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// Write a sample .xdg-volume-info file and check that it can be read.
func TestXDGVolumeInfo(t *testing.T) {
const expected = "some-volume name *()! $"
content := TemplateXDGVolumeInfo(expected)
file, _ := os.CreateTemp("", "onedriver-test-*")
os.WriteFile(file.Name(), []byte(content), 0600)
driveName, err := GetXDGVolumeInfoName(file.Name())
require.NoError(t, err)
assert.Equal(t, expected, driveName)
}
203 changes: 150 additions & 53 deletions cmd/onedriver-launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "C"

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"unsafe"
Expand Down Expand Up @@ -57,6 +58,9 @@ func main() {
os.Exit(0)
}

// loading config can emit an unformatted log message, so we do this first
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"})

// command line options override config options
config := common.LoadConfig(*configPath)
if *cacheDir != "" {
Expand All @@ -66,7 +70,6 @@ func main() {
config.LogLevel = *logLevel
}

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "15:04:05"})
zerolog.SetGlobalLevel(common.StringToLevel(config.LogLevel))

log.Info().Msgf("onedriver-launcher %s", common.Version())
Expand Down Expand Up @@ -242,55 +245,137 @@ func newMountRow(config common.Config, mount string) (*gtk.ListBoxRow, *gtk.Swit
escapedMount := unit.UnitNamePathEscape(mount)
unitName := systemd.TemplateUnit(systemd.OnedriverServiceTemplate, escapedMount)

var label *gtk.Label
tildePath := ui.EscapeHome(mount)
accountName, err := ui.GetAccountName(config.CacheDir, escapedMount)
driveName, err := common.GetXDGVolumeInfoName(filepath.Join(mount, ".xdg-volume-info"))
if err != nil {
log.Error().
Err(err).
Str("mountpoint", mount).
Msg("Could not determine acccount name.")
label, _ = gtk.LabelNew(tildePath)
} else {
Msg("Could not determine user-specified acccount name.")
}

tildePath := ui.EscapeHome(mount)
accountName, err := ui.GetAccountName(config.CacheDir, escapedMount)
label, _ := gtk.LabelNew("")
if driveName != "" {
// we have a user-assigned name for the user's drive
label.SetMarkup(fmt.Sprintf("%s <span style=\"italic\" weight=\"light\">(%s)</span> ",
driveName, tildePath,
))
} else if err == nil {
// fs isn't mounted, so just use user principal name from AAD
label, _ = gtk.LabelNew("")
label.SetMarkup(fmt.Sprintf("%s <span style=\"italic\" weight=\"light\">(%s)</span> ",
accountName, tildePath,
))
} else {
// something went wrong and all we have is the mountpoint name
log.Error().
Err(err).
Str("mountpoint", mount).
Msg("Could not determine user principal name.")
label, _ = gtk.LabelNew(tildePath)
}
box.PackStart(label, false, false, 5)

// create a button to delete the mountpoint
deleteMountpointBtn, _ := gtk.ButtonNewFromIconName("user-trash-symbolic", gtk.ICON_SIZE_BUTTON)
deleteMountpointBtn.SetTooltipText("Remove OneDrive account from local computer")
deleteMountpointBtn.Connect("clicked", func() {
log.Trace().
Str("signal", "clicked").
// a switch to start/stop the mountpoint
mountToggle, _ := gtk.SwitchNew()
active, err := systemd.UnitIsActive(unitName)
if err == nil {
mountToggle.SetActive(active)
} else {
log.Error().Err(err).Msg("Error checking unit active state.")
}
mountToggle.SetTooltipText("Mount or unmount selected OneDrive account")
mountToggle.SetVAlign(gtk.ALIGN_CENTER)
mountToggle.Connect("state-set", func() {
log.Info().
Str("signal", "state-set").
Str("mount", mount).
Str("unitName", unitName).
Msg("Request to delete mount.")
Bool("active", mountToggle.GetActive()).
Msg("Changing systemd unit active state.")
err := systemd.UnitSetActive(unitName, mountToggle.GetActive())
if err != nil {
log.Error().
Err(err).
Str("unit", unitName).
Msg("Could not change systemd unit active state.")
}
})

if ui.CancelDialog("Remove mountpoint?", nil) {
log.Info().
Str("signal", "clicked").
Str("mount", mount).
Str("unitName", unitName).
Msg("Deleting mount.")
systemd.UnitSetEnabled(unitName, false)
systemd.UnitSetActive(unitName, false)
mountpointSettingsBtn, _ := gtk.MenuButtonNew()
icon, _ := gtk.ImageNewFromIconName("emblem-system-symbolic", gtk.ICON_SIZE_BUTTON)
mountpointSettingsBtn.SetImage(icon)
popover, _ := gtk.PopoverNew(mountpointSettingsBtn)
mountpointSettingsBtn.SetPopover(popover)
popover.SetBorderWidth(8)
popoverBox, _ := gtk.BoxNew(gtk.ORIENTATION_VERTICAL, 5)

cachedir, _ := os.UserCacheDir()
os.RemoveAll(fmt.Sprintf("%s/onedriver/%s/", cachedir, escapedMount))
if accountName != "" {
accountLabel, _ := gtk.LabelNew(accountName)
popoverBox.Add(accountLabel)
}
// rename the mount by rewriting the .xdg-volume-info file
renameMountpointEntry, _ := gtk.EntryNew()
renameMountpointEntry.SetTooltipText("Change the label that your file browser uses for this drive")
renameMountpointEntry.SetText(driveName)
// runs on enter
renameMountpointEntry.Connect("activate", func(entry *gtk.Entry) {
newName, err := entry.GetText()
ctx := log.With().
Str("signal", "clicked").
Str("mount", mount).
Str("unitName", unitName).
Str("oldName", driveName).
Str("newName", newName).
Logger()
if err != nil {
ctx.Error().Err(err).Msg("Failed to get new drive name.")
return
}
if driveName == newName {
ctx.Info().Msg("New name is same as old name, ignoring.")
return
}
ctx.Info().
Msg("Renaming mount.")
popover.GrabFocus()

row.Destroy()
err = systemd.UnitSetActive(unitName, true)
if err != nil {
ctx.Error().Err(err).Msg("Failed to start mount for rename.")
return
}
mountToggle.SetActive(true)

if ui.PollUntilAvail(mount, -1) {
xdgVolumeInfo := common.TemplateXDGVolumeInfo(newName)
driveName = newName
//FIXME why does this not work???
err = ioutil.WriteFile(filepath.Join(mount, ".xdg-volume-info"), []byte(xdgVolumeInfo), 0644)
if err != nil {
ctx.Error().Err(err).Msg("Failed to write new mount name.")
return
}
} else {
ctx.Error().Err(err).Msg("Mount never became ready.")
}
// update label in UI now
label.SetMarkup(fmt.Sprintf("%s <span style=\"italic\" weight=\"light\">(%s)</span> ",
newName, tildePath,
))

ui.Dialog("Drive rename will take effect on next filesystem start.", gtk.MESSAGE_INFO, nil)
ctx.Info().Msg("Drive rename will take effect on next filesystem start.")
})
box.PackEnd(deleteMountpointBtn, false, false, 0)
popoverBox.Add(renameMountpointEntry)

separator, _ := gtk.SeparatorMenuItemNew()
popoverBox.Add(separator)

// create a button to enable/disable the mountpoint
unitEnabledBtn, _ := gtk.ToggleButtonNew()
enabledImg, _ := gtk.ImageNewFromIconName("object-select-symbolic", gtk.ICON_SIZE_BUTTON)
unitEnabledBtn.SetImage(enabledImg)
unitEnabledBtn.SetTooltipText("Start mountpoint on login")
unitEnabledBtn, _ := gtk.CheckButtonNewWithLabel(" Start drive on login")
unitEnabledBtn.SetTooltipText("Start this drive automatically when you login")
enabled, err := systemd.UnitIsEnabled(unitName)
if err == nil {
unitEnabledBtn.SetActive(enabled)
Expand All @@ -312,33 +397,45 @@ func newMountRow(config common.Config, mount string) (*gtk.ListBoxRow, *gtk.Swit
Msg("Could not change systemd unit enabled state.")
}
})
box.PackEnd(unitEnabledBtn, false, false, 0)
popoverBox.PackStart(unitEnabledBtn, false, true, 0)

// a switch to start/stop the mountpoint
mountToggle, _ := gtk.SwitchNew()
active, err := systemd.UnitIsActive(unitName)
if err == nil {
mountToggle.SetActive(active)
} else {
log.Error().Err(err).Msg("Error checking unit active state.")
}
mountToggle.SetTooltipText("Mount or unmount selected OneDrive account")
mountToggle.SetVAlign(gtk.ALIGN_CENTER)
mountToggle.Connect("state-set", func() {
log.Info().
Str("signal", "state-set").
// button to delete the mount
deleteMountpointBtn, _ := gtk.ModelButtonNew()
deleteMountpointBtn.SetLabel("Remove drive")
deleteMountpointBtn.SetTooltipText("Remove OneDrive account from local computer")
deleteMountpointBtn.Connect("clicked", func(button *gtk.ModelButton) {
log.Trace().
Str("signal", "clicked").
Str("mount", mount).
Str("unitName", unitName).
Bool("active", mountToggle.GetActive()).
Msg("Changing systemd unit active state.")
err := systemd.UnitSetActive(unitName, mountToggle.GetActive())
if err != nil {
log.Error().
Err(err).
Str("unit", unitName).
Msg("Could not change systemd unit active state.")
Msg("Request to delete drive.")

if ui.CancelDialog(nil, "<span weight=\"bold\">Remove drive?</span>",
"This will remove all data for this drive from your local computer. "+
"It can also be used to \"reset\" the drive to its original state.") {
log.Info().
Str("signal", "clicked").
Str("mount", mount).
Str("unitName", unitName).
Msg("Deleting mount.")
systemd.UnitSetEnabled(unitName, false)
systemd.UnitSetActive(unitName, false)

cachedir, _ := os.UserCacheDir()
os.RemoveAll(fmt.Sprintf("%s/onedriver/%s/", cachedir, escapedMount))

row.Destroy()
}
})
popoverBox.PackStart(deleteMountpointBtn, false, true, 0)

// ok show everything in the mount settings menu
popoverBox.ShowAll()
popover.Add(popoverBox)
popover.SetPosition(gtk.POS_BOTTOM)

// add all widgets to row in the right order
box.PackEnd(mountpointSettingsBtn, false, false, 0)
box.PackEnd(mountToggle, false, false, 0)

// name is used by "row-activated" callback
Expand Down Expand Up @@ -388,7 +485,7 @@ func newSettingsWindow(config *common.Config, configPath string) {
oldPath, _ := button.GetLabel()
oldPath = ui.UnescapeHome(oldPath)
path := ui.DirChooser("Select an empty directory to use for storage")
if !ui.CancelDialog("Remount all drives?", settingsWindow) {
if !ui.CancelDialog(settingsWindow, "Remount all drives?", "") {
return
}
log.Warn().
Expand Down
Loading
Loading