mirror of
https://github.com/ProtonMail/proton-bridge.git
synced 2025-12-11 05:06:51 +00:00
GODT-2229: Own the full path for gluon and do not change Database path.
This commit is contained in:
committed by
Leander Beernaert
parent
8d9db83a87
commit
0580842ad2
2
go.mod
2
go.mod
@ -5,7 +5,7 @@ go 1.18
|
|||||||
require (
|
require (
|
||||||
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
github.com/0xAX/notificator v0.0.0-20220220101646-ee9b8921e557
|
||||||
github.com/Masterminds/semver/v3 v3.1.1
|
github.com/Masterminds/semver/v3 v3.1.1
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20230106095250-7e99ea4da61e
|
github.com/ProtonMail/gluon v0.14.2-0.20230111132924-ace48198e45a
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a
|
||||||
github.com/ProtonMail/go-proton-api v0.2.4-0.20230112102613-6ad201cdb337
|
github.com/ProtonMail/go-proton-api v0.2.4-0.20230112102613-6ad201cdb337
|
||||||
github.com/ProtonMail/go-rfc5322 v0.11.0
|
github.com/ProtonMail/go-rfc5322 v0.11.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@ -28,8 +28,8 @@ github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs
|
|||||||
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0 h1:+kvUIpwWcbtP3WFv5sSvkFn/XLzSqPOB5AAthuk9xPk=
|
||||||
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
github.com/ProtonMail/docker-credential-helpers v1.1.0/go.mod h1:mK0aBveCxhnQ756AmaTfXMZDeULvheYVhF/MWMErN5g=
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20230106095250-7e99ea4da61e h1://xRNjGTAMXw2U91MtqPc4krUtxQmt2+4z1oYrBaOWU=
|
github.com/ProtonMail/gluon v0.14.2-0.20230111132924-ace48198e45a h1:yx/1F4jGMLVTUVeycIGwe90l5YQWrvoTbPOYWC4FLkY=
|
||||||
github.com/ProtonMail/gluon v0.14.2-0.20230106095250-7e99ea4da61e/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
github.com/ProtonMail/gluon v0.14.2-0.20230111132924-ace48198e45a/go.mod h1:z2AxLIiBCT1K+0OBHyaDI7AEaO5qI6/BEC2TE42vs4Q=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:D+aZah+k14Gn6kmL7eKxoo/4Dr/lK3ChBcwce2+SQP4=
|
||||||
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
github.com/ProtonMail/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:oTGdE7/DlWIr23G0IKW3OXK9wZ5Hw1GGiaJFccTvZi4=
|
||||||
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
|
||||||
|
|||||||
@ -221,8 +221,14 @@ func newBridge(
|
|||||||
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
|
return nil, fmt.Errorf("failed to get Gluon directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gluonDBDir, err := locator.ProvideGluonPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get Gluon Database directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
imapServer, err := newIMAPServer(
|
imapServer, err := newIMAPServer(
|
||||||
gluonDir,
|
gluonDir,
|
||||||
|
gluonDBDir,
|
||||||
curVersion,
|
curVersion,
|
||||||
tlsConfig,
|
tlsConfig,
|
||||||
reporter,
|
reporter,
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
"github.com/ProtonMail/proton-bridge/v3/internal/bridge"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
"github.com/ProtonMail/proton-bridge/v3/internal/certs"
|
||||||
|
"github.com/ProtonMail/proton-bridge/v3/internal/constants"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
"github.com/ProtonMail/proton-bridge/v3/internal/cookies"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
"github.com/ProtonMail/proton-bridge/v3/internal/events"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
"github.com/ProtonMail/proton-bridge/v3/internal/focus"
|
||||||
@ -45,6 +46,7 @@ import (
|
|||||||
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
"github.com/ProtonMail/proton-bridge/v3/internal/vault"
|
||||||
"github.com/ProtonMail/proton-bridge/v3/tests"
|
"github.com/ProtonMail/proton-bridge/v3/tests"
|
||||||
"github.com/bradenaw/juniper/xslices"
|
"github.com/bradenaw/juniper/xslices"
|
||||||
|
"github.com/emersion/go-imap/client"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -349,7 +351,7 @@ func TestBridge_BadVaultKey(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridge_MissingGluonDir(t *testing.T) {
|
func TestBridge_MissingGluonStore(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
var gluonDir string
|
var gluonDir string
|
||||||
|
|
||||||
@ -361,13 +363,36 @@ func TestBridge_MissingGluonDir(t *testing.T) {
|
|||||||
require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
|
require.NoError(t, bridge.SetGluonDir(ctx, t.TempDir()))
|
||||||
|
|
||||||
// Get the gluon dir.
|
// Get the gluon dir.
|
||||||
gluonDir = bridge.GetGluonDir()
|
gluonDir = bridge.GetGluonCacheDir()
|
||||||
})
|
})
|
||||||
|
|
||||||
// The user removes the gluon dir while bridge is not running.
|
// The user removes the gluon dir while bridge is not running.
|
||||||
require.NoError(t, os.RemoveAll(gluonDir))
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
// Bridge starts but can't find the gluon dir; there should be no error.
|
// Bridge starts but can't find the gluon store dir; there should be no error.
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
|
// ...
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBridge_MissingGluonDatabase(t *testing.T) {
|
||||||
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
|
var gluonDir string
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
|
_, err := bridge.LoginFull(context.Background(), username, password, nil, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Get the gluon dir.
|
||||||
|
gluonDir, err = bridge.GetGluonConfigDir()
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
// The user removes the gluon dir while bridge is not running.
|
||||||
|
require.NoError(t, os.RemoveAll(gluonDir))
|
||||||
|
|
||||||
|
// Bridge starts but can't find the gluon database dir; there should be no error.
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// ...
|
// ...
|
||||||
})
|
})
|
||||||
@ -456,41 +481,80 @@ func TestBridge_FactoryReset(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridge_ChangeCacheDirectoryFailsBetweenDifferentVolumes(t *testing.T) {
|
func TestBridge_InitGluonDirectory(t *testing.T) {
|
||||||
if runtime.GOOS != "windows" {
|
|
||||||
t.Skip("Test only necessary on windows")
|
|
||||||
}
|
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
// Change directory
|
configDir, err := b.GetGluonConfigDir()
|
||||||
err := bridge.SetGluonDir(ctx, "XX:\\")
|
require.NoError(t, err)
|
||||||
require.Error(t, err)
|
|
||||||
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
|
||||||
|
require.False(t, os.IsNotExist(err))
|
||||||
|
|
||||||
|
_, err = os.ReadDir(bridge.ApplyGluonDBPathSuffix(configDir))
|
||||||
|
require.False(t, os.IsNotExist(err))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
func TestBridge_ChangeCacheDirectory(t *testing.T) {
|
||||||
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
withEnv(t, func(ctx context.Context, s *server.Server, netCtl *proton.NetCtl, locator bridge.Locator, vaultKey []byte) {
|
||||||
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(bridge *bridge.Bridge, mocks *bridge.Mocks) {
|
userID, addrID, err := s.CreateUser("imap", password)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
labelID, err := s.CreateLabel(userID, "folder", "", proton.LabelTypeFolder)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
withClient(ctx, t, s, "imap", password, func(ctx context.Context, c *proton.Client) {
|
||||||
|
createNumMessages(ctx, t, c, addrID, labelID, 10)
|
||||||
|
})
|
||||||
|
|
||||||
|
withBridge(ctx, t, s.GetHostURL(), netCtl, locator, vaultKey, func(b *bridge.Bridge, mocks *bridge.Mocks) {
|
||||||
newCacheDir := t.TempDir()
|
newCacheDir := t.TempDir()
|
||||||
currentCacheDir := bridge.GetGluonDir()
|
currentCacheDir := b.GetGluonCacheDir()
|
||||||
|
configDir, err := b.GetGluonConfigDir()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Login the user.
|
// Login the user.
|
||||||
userID, err := bridge.LoginFull(ctx, username, password, nil, nil)
|
syncCh, done := chToType[events.Event, events.SyncFinished](b.GetEvents(events.SyncFinished{}))
|
||||||
|
defer done()
|
||||||
|
userID, err := b.LoginFull(ctx, "imap", password, nil, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, userID, (<-syncCh).UserID)
|
||||||
|
|
||||||
// The user is now connected.
|
// The user is now connected.
|
||||||
require.Equal(t, []string{userID}, bridge.GetUserIDs())
|
require.Equal(t, []string{userID}, b.GetUserIDs())
|
||||||
require.Equal(t, []string{userID}, getConnectedUserIDs(t, bridge))
|
require.Equal(t, []string{userID}, getConnectedUserIDs(t, b))
|
||||||
|
|
||||||
// Change directory
|
// Change directory
|
||||||
err = bridge.SetGluonDir(ctx, newCacheDir)
|
err = b.SetGluonDir(ctx, newCacheDir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = os.ReadDir(currentCacheDir)
|
// Old store should no more exists.
|
||||||
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(currentCacheDir))
|
||||||
require.True(t, os.IsNotExist(err))
|
require.True(t, os.IsNotExist(err))
|
||||||
|
// Database should not have changed.
|
||||||
|
_, err = os.ReadDir(bridge.ApplyGluonDBPathSuffix(configDir))
|
||||||
|
require.False(t, os.IsNotExist(err))
|
||||||
|
|
||||||
require.Equal(t, newCacheDir, bridge.GetGluonDir())
|
// New path should have Gluon sub-folder.
|
||||||
|
require.Equal(t, filepath.Join(newCacheDir, "gluon"), b.GetGluonCacheDir())
|
||||||
|
// And store should be inside it.
|
||||||
|
_, err = os.ReadDir(bridge.ApplyGluonCachePathSuffix(b.GetGluonCacheDir()))
|
||||||
|
require.False(t, os.IsNotExist(err))
|
||||||
|
|
||||||
|
// We should be able to fetch.
|
||||||
|
info, err := b.GetUserInfo(userID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, info.State == bridge.Connected)
|
||||||
|
|
||||||
|
client, err := client.Dial(fmt.Sprintf("%v:%v", constants.Host, b.GetIMAPPort()))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, client.Login(info.Addresses[0], string(info.BridgePass)))
|
||||||
|
defer func() { _ = client.Logout() }()
|
||||||
|
|
||||||
|
status, err := client.Select(`Folders/folder`, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint32(10), status.Messages)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
package bridge
|
package bridge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
)
|
||||||
@ -62,3 +64,75 @@ func moveFile(from, to string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func copyDir(from, to string) error {
|
||||||
|
entries, err := os.ReadDir(from)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := createIfNotExists(to, 0o700); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
sourcePath := filepath.Join(from, entry.Name())
|
||||||
|
destPath := filepath.Join(to, entry.Name())
|
||||||
|
|
||||||
|
if entry.IsDir() {
|
||||||
|
if err := copyDir(sourcePath, destPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := copyFile(sourcePath, destPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyFile(srcFile, dstFile string) error {
|
||||||
|
out, err := os.Create(filepath.Clean(dstFile))
|
||||||
|
defer func(out *os.File) {
|
||||||
|
_ = out.Close()
|
||||||
|
}(out)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
in, err := os.Open(filepath.Clean(srcFile))
|
||||||
|
defer func(in *os.File) {
|
||||||
|
_ = in.Close()
|
||||||
|
}(in)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exists(filePath string) bool {
|
||||||
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func createIfNotExists(dir string, perm os.FileMode) error {
|
||||||
|
if exists(dir) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(dir, perm); err != nil {
|
||||||
|
return fmt.Errorf("failed to create directory: '%s', error: '%s'", dir, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -199,13 +199,13 @@ func (bridge *Bridge) handleIMAPEvent(event imapEvents.Event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getGluonDir(encVault *vault.Vault) (string, error) {
|
func getGluonDir(encVault *vault.Vault) (string, error) {
|
||||||
empty, exists, err := isEmpty(encVault.GetGluonDir())
|
empty, exists, err := isEmpty(encVault.GetGluonCacheDir())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
|
return "", fmt.Errorf("failed to check if gluon dir is empty: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
if err := os.MkdirAll(encVault.GetGluonDir(), 0o700); err != nil {
|
if err := os.MkdirAll(encVault.GetGluonCacheDir(), 0o700); err != nil {
|
||||||
return "", fmt.Errorf("failed to create gluon dir: %w", err)
|
return "", fmt.Errorf("failed to create gluon dir: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,12 +218,20 @@ func getGluonDir(encVault *vault.Vault) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return encVault.GetGluonDir(), nil
|
return encVault.GetGluonCacheDir(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyGluonCachePathSuffix(basePath string) string {
|
||||||
|
return filepath.Join(basePath, "backend", "store")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApplyGluonDBPathSuffix(basePath string) string {
|
||||||
|
return filepath.Join(basePath, "backend", "db")
|
||||||
}
|
}
|
||||||
|
|
||||||
// nolint:funlen
|
// nolint:funlen
|
||||||
func newIMAPServer(
|
func newIMAPServer(
|
||||||
gluonDir string,
|
gluonCacheDir, gluonDBDir string,
|
||||||
version *semver.Version,
|
version *semver.Version,
|
||||||
tlsConfig *tls.Config,
|
tlsConfig *tls.Config,
|
||||||
reporter reporter.Reporter,
|
reporter reporter.Reporter,
|
||||||
@ -231,8 +239,12 @@ func newIMAPServer(
|
|||||||
eventCh chan<- imapEvents.Event,
|
eventCh chan<- imapEvents.Event,
|
||||||
tasks *async.Group,
|
tasks *async.Group,
|
||||||
) (*gluon.Server, error) {
|
) (*gluon.Server, error) {
|
||||||
|
gluonCacheDir = ApplyGluonCachePathSuffix(gluonCacheDir)
|
||||||
|
gluonDBDir = ApplyGluonDBPathSuffix(gluonDBDir)
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{
|
logrus.WithFields(logrus.Fields{
|
||||||
"gluonDir": gluonDir,
|
"gluonStore": gluonCacheDir,
|
||||||
|
"gluonDB": gluonDBDir,
|
||||||
"version": version,
|
"version": version,
|
||||||
"logClient": logClient,
|
"logClient": logClient,
|
||||||
"logServer": logServer,
|
"logServer": logServer,
|
||||||
@ -263,7 +275,8 @@ func newIMAPServer(
|
|||||||
|
|
||||||
imapServer, err := gluon.New(
|
imapServer, err := gluon.New(
|
||||||
gluon.WithTLS(tlsConfig),
|
gluon.WithTLS(tlsConfig),
|
||||||
gluon.WithDataDir(gluonDir),
|
gluon.WithDataDir(gluonCacheDir),
|
||||||
|
gluon.WithDatabaseDir(gluonDBDir),
|
||||||
gluon.WithStoreBuilder(new(storeBuilder)),
|
gluon.WithStoreBuilder(new(storeBuilder)),
|
||||||
gluon.WithLogger(imapClientLog, imapServerLog),
|
gluon.WithLogger(imapClientLog, imapServerLog),
|
||||||
getGluonVersionInfo(version),
|
getGluonVersionInfo(version),
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
@ -114,38 +115,47 @@ func (bridge *Bridge) SetSMTPSSL(newSSL bool) error {
|
|||||||
return bridge.restartSMTP()
|
return bridge.restartSMTP()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) GetGluonDir() string {
|
func (bridge *Bridge) GetGluonCacheDir() string {
|
||||||
return bridge.vault.GetGluonDir()
|
return bridge.vault.GetGluonCacheDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) GetGluonConfigDir() (string, error) {
|
||||||
|
return bridge.locator.ProvideGluonPath()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error {
|
func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error {
|
||||||
return safe.RLockRet(func() error {
|
return safe.RLockRet(func() error {
|
||||||
currentGluonDir := bridge.GetGluonDir()
|
currentGluonDir := bridge.GetGluonCacheDir()
|
||||||
|
newGluonDir = filepath.Join(newGluonDir, "gluon")
|
||||||
if newGluonDir == currentGluonDir {
|
if newGluonDir == currentGluonDir {
|
||||||
return fmt.Errorf("new gluon dir is the same as the old one")
|
return fmt.Errorf("new gluon dir is the same as the old one")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentVolumeName := filepath.VolumeName(currentGluonDir)
|
if err := bridge.stopEventLoops(); err != nil {
|
||||||
newVolumeName := filepath.VolumeName(newGluonDir)
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
err := bridge.startEventLoops(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
if currentVolumeName != newVolumeName {
|
if err := bridge.moveGluonCacheDir(currentGluonDir, newGluonDir); err != nil {
|
||||||
return fmt.Errorf("it's currently not possible to move the cache between different volumes")
|
logrus.WithError(err).Error("failed to move GluonCacheDir")
|
||||||
|
if err := bridge.vault.SetGluonDir(currentGluonDir); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bridge.closeIMAP(context.Background()); err != nil {
|
gluonDBDir, err := bridge.GetGluonConfigDir()
|
||||||
return fmt.Errorf("failed to close IMAP: %w", err)
|
if err != nil {
|
||||||
}
|
panic(fmt.Errorf("failed to get Gluon Database directory: %w", err))
|
||||||
|
|
||||||
if err := moveDir(bridge.GetGluonDir(), newGluonDir); err != nil {
|
|
||||||
return fmt.Errorf("failed to move gluon dir: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
|
|
||||||
return fmt.Errorf("failed to set new gluon dir: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
imapServer, err := newIMAPServer(
|
imapServer, err := newIMAPServer(
|
||||||
bridge.vault.GetGluonDir(),
|
bridge.vault.GetGluonCacheDir(),
|
||||||
|
gluonDBDir,
|
||||||
bridge.curVersion,
|
bridge.curVersion,
|
||||||
bridge.tlsConfig,
|
bridge.tlsConfig,
|
||||||
bridge.reporter,
|
bridge.reporter,
|
||||||
@ -155,11 +165,44 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
|
|||||||
bridge.tasks,
|
bridge.tasks,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to create new IMAP server: %w", err)
|
panic(fmt.Errorf("failed to create new IMAP server: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
bridge.imapServer = imapServer
|
bridge.imapServer = imapServer
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, bridge.usersLock)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) moveGluonCacheDir(oldGluonDir, newGluonDir string) error {
|
||||||
|
logrus.Infof("gluon cache moving from %s to %s", oldGluonDir, newGluonDir)
|
||||||
|
oldCacheDir := ApplyGluonCachePathSuffix(oldGluonDir)
|
||||||
|
if err := copyDir(oldCacheDir, ApplyGluonCachePathSuffix(newGluonDir)); err != nil {
|
||||||
|
return fmt.Errorf("failed to copy gluon dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bridge.vault.SetGluonDir(newGluonDir); err != nil {
|
||||||
|
return fmt.Errorf("failed to set new gluon cache dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.RemoveAll(oldCacheDir); err != nil {
|
||||||
|
logrus.WithError(err).Error("failed to remove old gluon cache dir")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) stopEventLoops() error {
|
||||||
|
if err := bridge.closeIMAP(context.Background()); err != nil {
|
||||||
|
return fmt.Errorf("failed to close IMAP: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := bridge.closeSMTP(); err != nil {
|
||||||
|
return fmt.Errorf("failed to close SMTP: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bridge *Bridge) startEventLoops(ctx context.Context) error {
|
||||||
for _, user := range bridge.users {
|
for _, user := range bridge.users {
|
||||||
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
if err := bridge.addIMAPUser(ctx, user); err != nil {
|
||||||
return fmt.Errorf("failed to add users to new IMAP server: %w", err)
|
return fmt.Errorf("failed to add users to new IMAP server: %w", err)
|
||||||
@ -167,11 +210,13 @@ func (bridge *Bridge) SetGluonDir(ctx context.Context, newGluonDir string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := bridge.serveIMAP(); err != nil {
|
if err := bridge.serveIMAP(); err != nil {
|
||||||
return fmt.Errorf("failed to serve IMAP: %w", err)
|
panic(fmt.Errorf("failed to serve IMAP: %w", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := bridge.serveSMTP(); err != nil {
|
||||||
|
panic(fmt.Errorf("failed to serve SMTP: %w", err))
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}, bridge.usersLock)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bridge *Bridge) GetProxyAllowed() bool {
|
func (bridge *Bridge) GetProxyAllowed() bool {
|
||||||
|
|||||||
@ -399,6 +399,9 @@ func createMessages(ctx context.Context, t *testing.T, c *proton.Client, addrID,
|
|||||||
_, addrKRs, err := proton.Unlock(user, addr, keyPass)
|
_, addrKRs, err := proton.Unlock(user, addr, keyPass)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, ok := addrKRs[addrID]
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
res, err := stream.Collect(ctx, c.ImportMessages(
|
res, err := stream.Collect(ctx, c.ImportMessages(
|
||||||
ctx,
|
ctx,
|
||||||
addrKRs[addrID],
|
addrKRs[addrID],
|
||||||
|
|||||||
@ -196,7 +196,7 @@ func (f *frontendCLI) showAllMail(c *ishell.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
|
func (f *frontendCLI) setGluonLocation(c *ishell.Context) {
|
||||||
if gluonDir := f.bridge.GetGluonDir(); gluonDir != "" {
|
if gluonDir := f.bridge.GetGluonCacheDir(); gluonDir != "" {
|
||||||
f.Println("The current message cache location is:", gluonDir)
|
f.Println("The current message cache location is:", gluonDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -591,7 +591,7 @@ func (s *Service) IsAutomaticUpdateOn(ctx context.Context, _ *emptypb.Empty) (*w
|
|||||||
func (s *Service) DiskCachePath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
func (s *Service) DiskCachePath(ctx context.Context, _ *emptypb.Empty) (*wrapperspb.StringValue, error) {
|
||||||
s.log.Debug("DiskCachePath")
|
s.log.Debug("DiskCachePath")
|
||||||
|
|
||||||
return wrapperspb.String(s.bridge.GetGluonDir()), nil
|
return wrapperspb.String(s.bridge.GetGluonCacheDir()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) SetDiskCachePath(ctx context.Context, newPath *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
func (s *Service) SetDiskCachePath(ctx context.Context, newPath *wrapperspb.StringValue) (*emptypb.Empty, error) {
|
||||||
@ -609,14 +609,14 @@ func (s *Service) SetDiskCachePath(ctx context.Context, newPath *wrapperspb.Stri
|
|||||||
path = path[1:]
|
path = path[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
if path != s.bridge.GetGluonDir() {
|
if path != s.bridge.GetGluonCacheDir() {
|
||||||
if err := s.bridge.SetGluonDir(context.Background(), path); err != nil {
|
if err := s.bridge.SetGluonDir(context.Background(), path); err != nil {
|
||||||
s.log.WithError(err).Error("The local cache location could not be changed.")
|
s.log.WithError(err).Error("The local cache location could not be changed.")
|
||||||
_ = s.SendEvent(NewDiskCacheErrorEvent(DiskCacheErrorType_CANT_MOVE_DISK_CACHE_ERROR))
|
_ = s.SendEvent(NewDiskCacheErrorEvent(DiskCacheErrorType_CANT_MOVE_DISK_CACHE_ERROR))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = s.SendEvent(NewDiskCachePathChangedEvent(s.bridge.GetGluonDir()))
|
_ = s.SendEvent(NewDiskCachePathChangedEvent(s.bridge.GetGluonCacheDir()))
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@ -57,7 +57,7 @@ func TestMigrate(t *testing.T) {
|
|||||||
require.False(t, corrupt)
|
require.False(t, corrupt)
|
||||||
|
|
||||||
// Check the migrated vault.
|
// Check the migrated vault.
|
||||||
require.Equal(t, "v2.3.x-gluon-dir", s.GetGluonDir())
|
require.Equal(t, "v2.3.x-gluon-dir", s.GetGluonCacheDir())
|
||||||
require.Equal(t, 1234, s.GetIMAPPort())
|
require.Equal(t, 1234, s.GetIMAPPort())
|
||||||
require.Equal(t, 5678, s.GetSMTPPort())
|
require.Equal(t, 5678, s.GetSMTPPort())
|
||||||
|
|
||||||
|
|||||||
@ -70,8 +70,8 @@ func (vault *Vault) SetSMTPSSL(ssl bool) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGluonDir sets the directory where the gluon should store its data.
|
// GetGluonCacheDir sets the directory where the gluon should store its data.
|
||||||
func (vault *Vault) GetGluonDir() string {
|
func (vault *Vault) GetGluonCacheDir() string {
|
||||||
return vault.get().Settings.GluonDir
|
return vault.get().Settings.GluonDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,13 +67,13 @@ func TestVault_Settings_GluonDir(t *testing.T) {
|
|||||||
require.False(t, corrupt)
|
require.False(t, corrupt)
|
||||||
|
|
||||||
// Check the default gluon dir.
|
// Check the default gluon dir.
|
||||||
require.Equal(t, "/path/to/gluon", s.GetGluonDir())
|
require.Equal(t, "/path/to/gluon", s.GetGluonCacheDir())
|
||||||
|
|
||||||
// Modify the gluon dir.
|
// Modify the gluon dir.
|
||||||
require.NoError(t, s.SetGluonDir("/tmp/gluon"))
|
require.NoError(t, s.SetGluonDir("/tmp/gluon"))
|
||||||
|
|
||||||
// Check the new gluon dir.
|
// Check the new gluon dir.
|
||||||
require.Equal(t, "/tmp/gluon", s.GetGluonDir())
|
require.Equal(t, "/tmp/gluon", s.GetGluonCacheDir())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVault_Settings_UpdateChannel(t *testing.T) {
|
func TestVault_Settings_UpdateChannel(t *testing.T) {
|
||||||
|
|||||||
@ -89,7 +89,7 @@ func TestVault_Reset(t *testing.T) {
|
|||||||
require.Equal(t, 5678, s.GetSMTPPort())
|
require.Equal(t, 5678, s.GetSMTPPort())
|
||||||
|
|
||||||
// Reset.
|
// Reset.
|
||||||
require.NoError(t, s.Reset(s.GetGluonDir()))
|
require.NoError(t, s.Reset(s.GetGluonCacheDir()))
|
||||||
|
|
||||||
// The data is gone.
|
// The data is gone.
|
||||||
require.Equal(t, 1143, s.GetIMAPPort())
|
require.Equal(t, 1143, s.GetIMAPPort())
|
||||||
|
|||||||
@ -1,6 +1,10 @@
|
|||||||
Feature: Bridge can fully sync an account
|
Feature: Bridge can fully sync an account
|
||||||
Background:
|
Background:
|
||||||
Given there exists an account with username "[user:user]" and password "password"
|
Given there exists an account with username "[user:user]" and password "password"
|
||||||
|
And the address "[user:user]@[domain]" of account "[user:user]" has the following messages in "Inbox":
|
||||||
|
| from | to | subject | unread |
|
||||||
|
| john.doe@mail.com | [user:user]@[domain] | foo | false |
|
||||||
|
| jane.doe@mail.com | name@[domain] | bar | true |
|
||||||
And the account "[user:user]" has 20 custom folders
|
And the account "[user:user]" has 20 custom folders
|
||||||
And the account "[user:user]" has 60 custom labels
|
And the account "[user:user]" has 60 custom labels
|
||||||
When bridge starts
|
When bridge starts
|
||||||
@ -13,5 +17,9 @@ Feature: Bridge can fully sync an account
|
|||||||
Scenario: The user changes the gluon path
|
Scenario: The user changes the gluon path
|
||||||
When the user changes the gluon path
|
When the user changes the gluon path
|
||||||
And user "[user:user]" connects and authenticates IMAP client "2"
|
And user "[user:user]" connects and authenticates IMAP client "2"
|
||||||
Then IMAP client "2" counts 20 mailboxes under "Folders"
|
Then IMAP client "2" sees the following messages in "INBOX":
|
||||||
|
| from | to | subject | unread |
|
||||||
|
| john.doe@mail.com | [user:user]@[domain] | foo | false |
|
||||||
|
| jane.doe@mail.com | name@[domain] | bar | true |
|
||||||
|
And IMAP client "2" counts 20 mailboxes under "Folders"
|
||||||
And IMAP client "2" counts 60 mailboxes under "Labels"
|
And IMAP client "2" counts 60 mailboxes under "Labels"
|
||||||
Reference in New Issue
Block a user