Skip to content

Commit

Permalink
Add drive rename option (#394)
Browse files Browse the repository at this point in the history
* preliminary rename menu

* drive rename works

* remove unneeded statements

* update go version to fix ci

* add build tags for ci

* update actions to fix deprecation warnings
  • Loading branch information
jstaf committed Apr 20, 2024
1 parent 863bb94 commit f07678f
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 78 deletions.
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

0 comments on commit f07678f

Please sign in to comment.