forked from Silverfish/proton-bridge
Launcher, app/base, sentry, update service
This commit is contained in:
191
internal/locations/locations.go
Normal file
191
internal/locations/locations.go
Normal file
@ -0,0 +1,191 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// Package locations implements a type that provides cross-platform access to
|
||||
// standard filesystem locations, including config, cache and log directories.
|
||||
package locations
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/ProtonMail/proton-bridge/pkg/files"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Locations provides cross-platform access to standard locations.
|
||||
// On linux:
|
||||
// - settings: ~/.config/protonmail/<app>
|
||||
// - logs: ~/.cache/protonmail/<app>/logs
|
||||
// - cache: ~/.cache/protonmail/<app>/cache
|
||||
// - updates: ~/.cache/protonmail/<app>/updates
|
||||
// - lockfile: ~/.cache/protonmail/<app>/<app>.lock
|
||||
type Locations struct {
|
||||
userConfig, userCache string
|
||||
configName string
|
||||
}
|
||||
|
||||
type appDirsProvider interface {
|
||||
UserConfig() string
|
||||
UserCache() string
|
||||
}
|
||||
|
||||
func New(appDirs appDirsProvider, configName string) *Locations {
|
||||
return &Locations{
|
||||
userConfig: appDirs.UserConfig(),
|
||||
userCache: appDirs.UserCache(),
|
||||
configName: configName,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLockFile returns the path to the lock file (e.g. ~/.cache/<company>/<app>/<app>.lock).
|
||||
func (l *Locations) GetLockFile() string {
|
||||
return filepath.Join(l.userCache, l.configName+".lock")
|
||||
}
|
||||
|
||||
// GetLicenseFilePath returns path to liense file.
|
||||
func (l *Locations) GetLicenseFilePath() string {
|
||||
path := l.getLicenseFilePath()
|
||||
logrus.WithField("path", path).Info("License file path")
|
||||
return path
|
||||
}
|
||||
|
||||
func (l *Locations) getLicenseFilePath() string {
|
||||
// User can install app to different location, or user can run it
|
||||
// directly from the package without installation, or it could be
|
||||
// automatically updated (app started from differenet location).
|
||||
// For all those cases, first let's check LICENSE next to the binary.
|
||||
path := filepath.Join(filepath.Dir(os.Args[0]), "LICENSE")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
|
||||
switch runtime.GOOS {
|
||||
case "linux":
|
||||
appName := l.configName
|
||||
if l.configName == "importExport" {
|
||||
appName = "import-export"
|
||||
}
|
||||
// Most Linux distributions.
|
||||
path := "/usr/share/doc/protonmail/" + appName + "/LICENSE"
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
// Arch distributions.
|
||||
return "/usr/share/licenses/protonmail-" + appName + "/LICENSE"
|
||||
case "darwin": //nolint[goconst]
|
||||
path := filepath.Join(filepath.Dir(os.Args[0]), "..", "Resources", "LICENSE")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
|
||||
appName := "ProtonMail Bridge.app"
|
||||
if l.configName == "importExport" {
|
||||
appName = "ProtonMail Import-Export.app"
|
||||
}
|
||||
return "/Applications/" + appName + "/Contents/Resources/LICENSE"
|
||||
case "windows":
|
||||
path := filepath.Join(filepath.Dir(os.Args[0]), "LICENSE.txt")
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return path
|
||||
}
|
||||
// This should not happen, Windows should be handled by relative
|
||||
// location to the binary above. This is just fallback which may
|
||||
// or may not work, depends where user installed the app and how
|
||||
// user started the app.
|
||||
return filepath.FromSlash("C:/Program Files/Proton Technologies AG/ProtonMail Bridge/LICENSE.txt")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ProvideSettingsPath returns a location for user settings (e.g. ~/.config/<company>/<app>).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideSettingsPath() (string, error) {
|
||||
if err := os.MkdirAll(l.getSettingsPath(), 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return l.getSettingsPath(), nil
|
||||
}
|
||||
|
||||
// ProvideLogsPath returns a location for user logs (e.g. ~/.cache/<company>/<app>/logs).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideLogsPath() (string, error) {
|
||||
if err := os.MkdirAll(l.getLogsPath(), 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return l.getLogsPath(), nil
|
||||
}
|
||||
|
||||
// ProvideCachePath returns a location for user cache dirs (e.g. ~/.cache/<company>/<app>/cache).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideCachePath() (string, error) {
|
||||
if err := os.MkdirAll(l.getCachePath(), 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return l.getCachePath(), nil
|
||||
}
|
||||
|
||||
// ProvideUpdatesPath returns a location for update files (e.g. ~/.cache/<company>/<app>/updates).
|
||||
// It creates it if it doesn't already exist.
|
||||
func (l *Locations) ProvideUpdatesPath() (string, error) {
|
||||
if err := os.MkdirAll(l.getUpdatesPath(), 0700); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return l.getUpdatesPath(), nil
|
||||
}
|
||||
|
||||
func (l *Locations) getSettingsPath() string {
|
||||
return l.userConfig
|
||||
}
|
||||
|
||||
func (l *Locations) getLogsPath() string {
|
||||
return filepath.Join(l.userCache, "logs")
|
||||
}
|
||||
|
||||
func (l *Locations) getCachePath() string {
|
||||
return filepath.Join(l.userCache, "cache")
|
||||
}
|
||||
|
||||
func (l *Locations) getUpdatesPath() string {
|
||||
return filepath.Join(l.userCache, "updates")
|
||||
}
|
||||
|
||||
// Clear removes everything except the lock file.
|
||||
func (l *Locations) Clear() error {
|
||||
return files.Remove(
|
||||
l.getSettingsPath(),
|
||||
l.getLogsPath(),
|
||||
l.getCachePath(),
|
||||
l.getUpdatesPath(),
|
||||
).Do()
|
||||
}
|
||||
|
||||
// Clean removes any unexpected files from the app cache folder
|
||||
// while leaving files in the standard locations untouched.
|
||||
func (l *Locations) Clean() error {
|
||||
return files.Remove(l.userCache).Except(
|
||||
l.GetLockFile(),
|
||||
l.getLogsPath(),
|
||||
l.getCachePath(),
|
||||
l.getUpdatesPath(),
|
||||
).Do()
|
||||
}
|
||||
152
internal/locations/locations_test.go
Normal file
152
internal/locations/locations_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2020 Proton Technologies AG
|
||||
//
|
||||
// This file is part of ProtonMail Bridge.
|
||||
//
|
||||
// ProtonMail Bridge is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// ProtonMail Bridge is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package locations
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeAppDirs struct {
|
||||
configDir, cacheDir string
|
||||
}
|
||||
|
||||
func (dirs *fakeAppDirs) UserConfig() string {
|
||||
return dirs.configDir
|
||||
}
|
||||
|
||||
func (dirs *fakeAppDirs) UserCache() string {
|
||||
return dirs.cacheDir
|
||||
}
|
||||
|
||||
func TestClearRemovesEverythingExceptLockFile(t *testing.T) {
|
||||
l := newTestLocations(t)
|
||||
|
||||
assert.NoError(t, l.Clear())
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.NoDirExists(t, l.getSettingsPath())
|
||||
assert.NoDirExists(t, l.getLogsPath())
|
||||
assert.NoDirExists(t, l.getCachePath())
|
||||
assert.NoDirExists(t, l.getUpdatesPath())
|
||||
}
|
||||
|
||||
func TestCleanLeavesStandardLocationsUntouched(t *testing.T) {
|
||||
l := newTestLocations(t)
|
||||
|
||||
createFilesInDir(t, l.getLogsPath(),
|
||||
"log1.txt",
|
||||
"log2.txt",
|
||||
)
|
||||
|
||||
assert.NoError(t, l.Clean())
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.DirExists(t, l.getSettingsPath())
|
||||
assert.DirExists(t, l.getLogsPath())
|
||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log1.txt"))
|
||||
assert.FileExists(t, filepath.Join(l.getLogsPath(), "log2.txt"))
|
||||
assert.DirExists(t, l.getCachePath())
|
||||
assert.DirExists(t, l.getUpdatesPath())
|
||||
}
|
||||
|
||||
func TestCleanRemovesUnexpectedFilesAndFolders(t *testing.T) {
|
||||
l := newTestLocations(t)
|
||||
|
||||
createFilesInDir(t, l.userCache,
|
||||
"unexpected1.txt",
|
||||
"dir1/unexpected2.txt",
|
||||
"dir1/unexpected3.txt",
|
||||
"dir2/unexpected4.txt",
|
||||
"dir3/dir4/unexpected5.txt",
|
||||
)
|
||||
|
||||
require.FileExists(t, filepath.Join(l.userCache, "unexpected1.txt"))
|
||||
require.FileExists(t, filepath.Join(l.userCache, "dir1", "unexpected2.txt"))
|
||||
require.FileExists(t, filepath.Join(l.userCache, "dir1", "unexpected3.txt"))
|
||||
require.FileExists(t, filepath.Join(l.userCache, "dir2", "unexpected4.txt"))
|
||||
require.FileExists(t, filepath.Join(l.userCache, "dir3", "dir4", "unexpected5.txt"))
|
||||
|
||||
assert.NoError(t, l.Clean())
|
||||
|
||||
assert.FileExists(t, l.GetLockFile())
|
||||
assert.DirExists(t, l.getSettingsPath())
|
||||
assert.DirExists(t, l.getLogsPath())
|
||||
assert.DirExists(t, l.getCachePath())
|
||||
assert.DirExists(t, l.getUpdatesPath())
|
||||
|
||||
assert.NoFileExists(t, filepath.Join(l.userCache, "unexpected1.txt"))
|
||||
assert.NoFileExists(t, filepath.Join(l.userCache, "dir1", "unexpected2.txt"))
|
||||
assert.NoFileExists(t, filepath.Join(l.userCache, "dir1", "unexpected3.txt"))
|
||||
assert.NoFileExists(t, filepath.Join(l.userCache, "dir2", "unexpected4.txt"))
|
||||
assert.NoFileExists(t, filepath.Join(l.userCache, "dir3", "dir4", "unexpected5.txt"))
|
||||
}
|
||||
|
||||
func newFakeAppDirs(t *testing.T) *fakeAppDirs {
|
||||
configDir, err := ioutil.TempDir("", "test-locations-config")
|
||||
require.NoError(t, err)
|
||||
|
||||
cacheDir, err := ioutil.TempDir("", "test-locations-cache")
|
||||
require.NoError(t, err)
|
||||
|
||||
return &fakeAppDirs{
|
||||
configDir: configDir,
|
||||
cacheDir: cacheDir,
|
||||
}
|
||||
}
|
||||
|
||||
func newTestLocations(t *testing.T) *Locations {
|
||||
l := New(newFakeAppDirs(t), "configName")
|
||||
|
||||
lock := l.GetLockFile()
|
||||
createFilesInDir(t, "", lock)
|
||||
require.FileExists(t, lock)
|
||||
|
||||
settings, err := l.ProvideSettingsPath()
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, settings)
|
||||
|
||||
logs, err := l.ProvideLogsPath()
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, logs)
|
||||
|
||||
cache, err := l.ProvideCachePath()
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, cache)
|
||||
|
||||
updates, err := l.ProvideUpdatesPath()
|
||||
require.NoError(t, err)
|
||||
require.DirExists(t, updates)
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func createFilesInDir(t *testing.T, dir string, files ...string) {
|
||||
for _, target := range files {
|
||||
require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, target)), 0700))
|
||||
|
||||
f, err := os.Create(filepath.Join(dir, target))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Close())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user