Skip to content

Commit

Permalink
preliminary rename menu
Browse files Browse the repository at this point in the history
  • Loading branch information
jstaf committed Nov 29, 2023
1 parent 1b8eac1 commit bc3e11c
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 66 deletions.
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)
}
193 changes: 138 additions & 55 deletions cmd/onedriver-launcher/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import "C"

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"time"
"unsafe"

"github.com/coreos/go-systemd/v22/unit"
Expand Down Expand Up @@ -242,55 +244,82 @@ 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.")

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)

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

row.Destroy()
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.")
}
})
box.PackEnd(deleteMountpointBtn, false, false, 0)

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)

if accountName != "" {
accountLabel, _ := gtk.LabelNew(accountName)
popoverBox.Add(accountLabel)
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 +341,87 @@ 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)

// 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").
popoverBox.PackStart(unitEnabledBtn, false, true, 0)

// rename the mount by rewriting the .xdg-volume-info file
renameMountpointBtn, _ := gtk.ModelButtonNew()
renameMountpointBtn.SetLabel("Change display name")
renameMountpointBtn.SetTooltipText("Change the label that your file browser uses for this drive")
renameMountpointBtn.Connect("clicked", func(button *gtk.ModelButton) {
newName := "test drive" //TODO get the new drive name
ctx := log.With().
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())
Str("name", newName).
Logger()
ctx.Info().
Msg("Renaming mount.")
err := systemd.UnitSetActive(unitName, true)
if err != nil {
log.Error().
Err(err).
Str("unit", unitName).
Msg("Could not change systemd unit active state.")
ctx.Error().Err(err).Msg("Failed to rename mount.")
return
}
mountToggle.SetActive(true)

if ui.PollUntilAvail(mount, -1) {
xdgVolumeInfo := common.TemplateXDGVolumeInfo(newName)
err = ioutil.WriteFile(filepath.Join(mount, ".xdg-volume-info"), []byte(xdgVolumeInfo), 0644)
if err != nil {
ctx.Error().Err(err).Msg("Failed to rename mount.")
}
} 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,
))
// and reset the drive again so people's file browsers actually pick it up
ctx.Info().Msg("Remounting drive to ensure file browser gets the new name.")
systemd.UnitSetActive(unitName, false)
time.Sleep(100 * time.Millisecond) // probably unnecessary
systemd.UnitSetActive(unitName, true)
})
popoverBox.PackStart(renameMountpointBtn, false, true, 0)

// 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).
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 +471,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
6 changes: 1 addition & 5 deletions cmd/onedriver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,11 +166,7 @@ func xdgVolumeInfo(filesystem *fs.Filesystem, auth *graph.Auth) {
log.Error().Err(err).Msg("Could not create .xdg-volume-info")
return
}

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

// just upload directly and shove it in the cache
// (since the fs isn't mounted yet)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/jstaf/onedriver
require (
github.com/coreos/go-systemd/v22 v22.3.2
github.com/godbus/dbus/v5 v5.0.6
github.com/gotk3/gotk3 v0.6.1
github.com/gotk3/gotk3 v0.6.2
github.com/hanwen/go-fuse/v2 v2.1.0
github.com/imdario/mergo v0.3.13
github.com/rs/zerolog v1.26.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ github.com/godbus/dbus/v5 v5.0.6 h1:mkgN1ofwASrYnJ5W6U/BxG15eXXXjirgZc7CLqkcaro=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gotk3/gotk3 v0.6.1 h1:GJ400a0ecEEWrzjBvzBzH+pB/esEMIGdB9zPSmBdoeo=
github.com/gotk3/gotk3 v0.6.1/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/gotk3/gotk3 v0.6.2 h1:sx/PjaKfKULJPTPq8p2kn2ZbcNFxpOJqi4VLzMbEOO8=
github.com/gotk3/gotk3 v0.6.2/go.mod h1:/hqFpkNa9T3JgNAE2fLvCdov7c5bw//FHNZrZ3Uv9/Q=
github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc=
github.com/hanwen/go-fuse v1.0.0/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok=
github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek=
Expand Down
17 changes: 12 additions & 5 deletions ui/widgets.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,18 @@ func Dialog(msg string, messageType gtk.MessageType, parentWindow gtk.IWindow) {

// CancelDialog creates a "Continue?" style message, and returns what the user
// selected
func CancelDialog(title string, parentWindow gtk.IWindow) bool {
dialog, _ := gtk.DialogNewWithButtons(title, parentWindow, gtk.DIALOG_MODAL,
[]interface{}{"Cancel", gtk.RESPONSE_CANCEL},
[]interface{}{"Continue", gtk.RESPONSE_ACCEPT},
func CancelDialog(parentWindow gtk.IWindow, primaryText, secondaryText string) bool {
dialog := gtk.MessageDialogNew(
parentWindow,
gtk.DIALOG_MODAL,
gtk.MESSAGE_WARNING,
gtk.BUTTONS_OK_CANCEL,
"",
)
dialog.SetMarkup(primaryText)
if secondaryText != "" {
dialog.FormatSecondaryMarkup(secondaryText)
}
defer dialog.Destroy()
return dialog.Run() == gtk.RESPONSE_ACCEPT
return dialog.Run() == gtk.RESPONSE_OK
}

0 comments on commit bc3e11c

Please sign in to comment.