aliyunpan/internal/config/pan_config.go
2024-03-03 23:20:27 +08:00

535 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Copyright (c) 2020 tickstep.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"fmt"
jsoniter "github.com/json-iterator/go"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan/cmder/cmdutil"
"github.com/tickstep/aliyunpan/cmder/cmdutil/jsonhelper"
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/homedir"
"github.com/tickstep/library-go/logger"
"github.com/tickstep/library-go/requester"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
const (
// EnvVerbose 启用调试环境变量
EnvVerbose = "ALIYUNPAN_VERBOSE"
// EnvConfigDir 配置路径环境变量
EnvConfigDir = "ALIYUNPAN_CONFIG_DIR"
// ConfigName 配置文件名
ConfigName = "aliyunpan_config.json"
// ConfigVersion 配置文件版本
ConfigVersion string = "1.0"
// DefaultFileUploadParallelNum 默认的文件上传并发数量
DefaultFileUploadParallelNum = 10
// MaxFileUploadParallelNum 最大文件上传并发数量。过大会被阿里云盘风控,导致无法上传
MaxFileUploadParallelNum = 20
// DefaultFileDownloadParallelNum 默认的文件下载并发数量
DefaultFileDownloadParallelNum = 5
// MaxFileDownloadParallelNum 最大文件下载并发数量。过大会被阿里云盘风控,导致无法下载
MaxFileDownloadParallelNum = 20
// DefaultTokenServiceWebHost 默认的token服务
DefaultTokenServiceWebHost = "https://api.tickstep.com"
//DefaultTokenServiceWebHost = "http://localhost:8977"
// DefaultVideoFileExtensions 默认的视频文件后缀
DefaultVideoFileExtensions = "mp4,flv,mkv,mov,rm,rmvb,wmv,wma,mv,asf,asx,mpg,mpeg,mpe,3gp,m4v,avi,vob"
// DefaultDeviceName 默认客户端名称
DefaultDeviceName = "Chrome浏览器"
// DefaultClientId 默认的clientId
DefaultClientId = "cf9f70e8fc61430f8ec5ab5cadf31375"
)
var (
CmdConfigVerbose = logger.New("CONFIG", EnvVerbose)
configFilePath = filepath.Join(GetConfigDir(), ConfigName)
// Config 配置信息, 由外部调用
Config = NewConfig(configFilePath)
)
type UpdateCheckInfo struct {
PreferUpdateSrv string `json:"preferUpdateSrv"` // 优先更新服务器github | tickstep
LatestVer string `json:"latestVer"` // 最后检测到的版本
CheckTime int64 `json:"checkTime"` // 最后检测的时间戳,单位为秒
}
// PanConfig 配置详情
type PanConfig struct {
ConfigVer string `json:"configVer"`
ActiveUID string `json:"activeUID"`
UserList PanUserList `json:"userList"`
CacheSize int `json:"cacheSize"` // 下载缓存
MaxDownloadParallel int `json:"maxDownloadParallel"` // 最大下载并发量,即同时下载文件最大数量
MaxUploadParallel int `json:"maxUploadParallel"` // 最大上传并发量,即同时上传文件最大数量
MaxDownloadRate int64 `json:"maxDownloadRate"` // 限制最大下载速度,单位 B/s, 即字节/每秒
MaxUploadRate int64 `json:"maxUploadRate"` // 限制最大上传速度,单位 B/s, 即字节/每秒
SaveDir string `json:"saveDir"` // 下载储存路径
Proxy string `json:"proxy"` // 代理
LocalAddrs string `json:"localAddrs"` // 本地网卡地址
PreferIPType string `json:"preferIPType"` // 优先IP类型IPv4或者IPv6
UpdateCheckInfo UpdateCheckInfo `json:"updateCheckInfo"`
VideoFileExtensions string `json:"videoFileExtensions"`
FileRecordConfig string `json:"fileRecordConfig"` // 上传、下载、同步文件的记录,包括失败和成功的
DeviceId string `json:"deviceId"` // 客户端ID用于标识登录客户端阿里单个账号最多允许10个客户端同时登录
DeviceName string `json:"deviceName"` // 客户端名称默认为Chrome浏览器
// Openapi客户端信息
ClientId string `json:"clientId"`
ClientSecret string `json:"clientSecret"`
configFilePath string
configFile *os.File
fileMu sync.Mutex
activeUser *PanUser
}
// NewConfig 返回 PanConfig 指针对象
func NewConfig(configFilePath string) *PanConfig {
c := &PanConfig{
configFilePath: configFilePath,
}
return c
}
// Init 初始化配置
func (c *PanConfig) Init() error {
return c.init()
}
// Reload 从文件重载配置
func (c *PanConfig) Reload() error {
return c.init()
}
// Close 关闭配置文件
func (c *PanConfig) Close() error {
if c.configFile != nil {
err := c.configFile.Close()
c.configFile = nil
return err
}
return nil
}
// Save 保存配置信息到配置文件
func (c *PanConfig) Save() error {
// 检测配置项是否合法, 不合法则自动修复
c.fix()
err := c.lazyOpenConfigFile()
if err != nil {
return err
}
c.fileMu.Lock()
defer c.fileMu.Unlock()
data, err := jsoniter.MarshalIndent(c, "", " ")
if err != nil {
// json数据生成失败
panic(err)
}
// 减掉多余的部分
err = c.configFile.Truncate(int64(len(data)))
if err != nil {
return err
}
_, err = c.configFile.Seek(0, os.SEEK_SET)
if err != nil {
return err
}
_, err = c.configFile.Write(data)
if err != nil {
return err
}
return nil
}
func (c *PanConfig) init() error {
if c.configFilePath == "" {
return ErrConfigFileNotExist
}
c.initDefaultConfig()
err := c.loadConfigFromFile()
if err != nil {
return err
}
// 设置全局代理
if c.Proxy != "" {
requester.SetGlobalProxy(c.Proxy)
}
// 设置本地网卡地址
if c.LocalAddrs != "" {
requester.SetLocalTCPAddrList(strings.Split(c.LocalAddrs, ",")...)
}
// 设置域名解析策略 IPv4 or IPv6
t := requester.IPAny
if strings.ToLower(c.PreferIPType) == "ipv4" {
t = requester.IPv4
} else if strings.ToLower(c.PreferIPType) == "ipv6" {
t = requester.IPv6
}
requester.SetPreferIPType(t)
return nil
}
// lazyOpenConfigFile 打开配置文件
func (c *PanConfig) lazyOpenConfigFile() (err error) {
if c.configFile != nil {
return nil
}
c.fileMu.Lock()
os.MkdirAll(filepath.Dir(c.configFilePath), 0755)
c.configFile, err = os.OpenFile(c.configFilePath, os.O_CREATE|os.O_RDWR, 0755)
c.fileMu.Unlock()
if err != nil {
if os.IsPermission(err) {
return ErrConfigFileNoPermission
}
if os.IsExist(err) {
return ErrConfigFileNotExist
}
return err
}
return nil
}
// loadConfigFromFile 载入配置
func (c *PanConfig) loadConfigFromFile() (err error) {
err = c.lazyOpenConfigFile()
if err != nil {
return err
}
// 未初始化
info, err := c.configFile.Stat()
if err != nil {
return err
}
if info.Size() == 0 {
err = c.Save()
return err
}
c.fileMu.Lock()
defer c.fileMu.Unlock()
_, err = c.configFile.Seek(0, os.SEEK_SET)
if err != nil {
return err
}
err = jsonhelper.UnmarshalData(c.configFile, c)
if err != nil {
return ErrConfigContentsParseError
}
if c.DeviceName == "" {
c.DeviceName = DefaultDeviceName
}
if c.ClientId == "" {
c.ClientId = DefaultClientId
}
return nil
}
func (c *PanConfig) initDefaultConfig() {
// 设置默认的下载路径
switch runtime.GOOS {
case "windows":
c.SaveDir = cmdutil.ExecutablePathJoin("Downloads")
case "android":
// TODO: 获取完整的的下载路径
c.SaveDir = "/sdcard/Download"
default:
dataPath, ok := os.LookupEnv("HOME")
if !ok {
CmdConfigVerbose.Warn("Environment HOME not set")
c.SaveDir = cmdutil.ExecutablePathJoin("Downloads")
} else {
c.SaveDir = filepath.Join(dataPath, "Downloads")
}
}
c.ConfigVer = ConfigVersion
c.VideoFileExtensions = DefaultVideoFileExtensions
c.DeviceId = RandomDeviceId() // 生成默认客户端ID
c.DeviceName = DefaultDeviceName
c.ClientId = DefaultClientId
c.FileRecordConfig = "1" // 默认开启
c.PreferIPType = "ipv4" // 默认优先IPv4
}
// GetConfigDir 获取配置路径
func GetConfigDir() string {
// 按照以下顺序依次获取配置目录
// 1.环境变量ALIYUNPAN_CONFIG_DIR => 2. /etc/aliyunpan/ => 3. ~/.aliyunpan/ => 4.当前程序目录
// 1. 从环境变量读取
configDir, ok := os.LookupEnv(EnvConfigDir)
if ok {
if filepath.IsAbs(configDir) {
logger.Verboseln("use config dir from ALIYUNPAN_CONFIG_DIR env: ", configDir)
return configDir
}
// 如果不是绝对路径, 从程序目录寻找
configDir = cmdutil.ExecutablePathJoin(configDir)
logger.Verboseln("use config dir from ALIYUNPAN_CONFIG_DIR env: ", configDir)
return configDir
} else {
// 2. /etc/aliyunpan/
if runtime.GOOS == "linux" {
cd := "/etc/aliyunpan"
if IsFolderExist(cd) {
logger.Verboseln("use config dir: ", cd)
return cd
}
}
// 3. ~/.aliyunpan/
if runtime.GOOS == "linux" || runtime.GOOS == "windows" {
cd, er := homedir.Expand("~/.aliyunpan")
if er == nil {
if IsFolderExist(cd) {
logger.Verboseln("use config dir: ", cd)
return cd
}
}
}
}
// 4.当前程序所在目录
configDir = cmdutil.ExecutablePathJoin("")
logger.Verboseln("use config dir: ", configDir)
return configDir
}
// GetPluginDir 获取插件文件夹路径
func GetPluginDir() string {
return strings.TrimSuffix(GetConfigDir(), "/") + "/plugin"
}
// GetPluginKvFile 获取插件KV键值对存储文件
func GetPluginKvFile() string {
return GetPluginDir() + "/kv.bolt"
}
// GetSyncDriveDir 获取同步备份的文件夹路径
func GetSyncDriveDir() string {
return strings.TrimSuffix(GetConfigDir(), "/") + "/sync_drive"
}
// GetLogDir 获取日志文件目录路径
func GetLogDir() string {
return strings.TrimSuffix(GetConfigDir(), "/") + "/logs"
}
// GetLogFilePath 获取日志文件路径
func GetLogFilePath() string {
dirPath := GetLogDir()
if b, e := utils.PathExists(dirPath); e == nil {
if !b {
os.MkdirAll(dirPath, 0755)
}
}
return dirPath + "/" + "aliyunpan_verbose.log"
}
// GetLockerDir 获取文件锁路径
func GetLockerDir() string {
return strings.TrimSuffix(GetConfigDir(), "/")
}
func (c *PanConfig) ActiveUser() *PanUser {
if c.activeUser == nil {
if c.UserList == nil {
return nil
}
if c.ActiveUID == "" {
return nil
}
for _, u := range c.UserList {
if u.UserId == c.ActiveUID {
if u.PanClient() == nil {
// restore client
user, err := SetupUserByCookie(u.OpenapiToken, u.WebapiToken,
u.TicketId, u.UserId,
c.DeviceId, c.DeviceName,
c.ClientId, c.ClientSecret)
if err != nil {
logger.Verboseln("setup user error")
return nil
}
u.panClient = user.panClient
u.Nickname = user.Nickname
u.DriveList = user.DriveList
// check workdir valid or not
if user.IsFileDriveActive() {
fe, err1 := u.PanClient().OpenapiPanClient().FileInfoByPath(u.ActiveDriveId, u.Workdir)
if err1 != nil {
// default to root
u.Workdir = "/"
u.WorkdirFileEntity = *aliyunpan.NewFileEntityForRootDir()
} else {
u.WorkdirFileEntity = *fe
}
} else if user.IsResourceDriveActive() {
fe, err1 := u.PanClient().OpenapiPanClient().FileInfoByPath(u.ActiveDriveId, u.ResourceWorkdir)
if err1 != nil {
// default to root
u.ResourceWorkdir = "/"
u.ResourceWorkdirFileEntity = *aliyunpan.NewFileEntityForRootDir()
} else {
u.ResourceWorkdirFileEntity = *fe
}
} else if user.IsAlbumDriveActive() {
fe, err1 := u.PanClient().WebapiPanClient().FileInfoByPath(u.ActiveDriveId, u.AlbumWorkdir)
if err1 != nil {
// default to root
u.AlbumWorkdir = "/"
u.AlbumWorkdirFileEntity = *aliyunpan.NewFileEntityForRootDir()
} else {
u.AlbumWorkdirFileEntity = *fe
}
}
}
c.activeUser = u
return u
}
}
return nil
}
return c.activeUser
}
func (c *PanConfig) SetActiveUser(user *PanUser) *PanUser {
needToInsert := true
for _, u := range c.UserList {
if u.UserId == user.UserId {
// update user info
u.Nickname = user.Nickname
u.WebapiToken = user.WebapiToken
u.OpenapiToken = user.OpenapiToken
u.TicketId = user.TicketId
u.UpdateClient(user.PanClient().OpenapiPanClient(), user.PanClient().WebapiPanClient())
needToInsert = false
break
}
}
if needToInsert {
// insert
c.UserList = append(c.UserList, user)
}
// setup active user
c.ActiveUID = user.UserId
// clear active user cache
c.activeUser = nil
// reload
return c.ActiveUser()
}
func (c *PanConfig) fix() {
}
// NumLogins 获取登录的用户数量
func (c *PanConfig) NumLogins() int {
return len(c.UserList)
}
// SwitchUser 切换登录用户
func (c *PanConfig) SwitchUser(uid, username string) (*PanUser, error) {
for _, u := range c.UserList {
if u.UserId == uid || u.AccountName == username {
return c.SetActiveUser(u), nil
}
}
return nil, fmt.Errorf("未找到指定的账号")
}
// DeleteUser 删除用户,并自动切换登录用户为用户列表第一个
func (c *PanConfig) DeleteUser(uid string) (*PanUser, error) {
for idx, u := range c.UserList {
if u.UserId == uid {
// delete user from user list
c.UserList = append(c.UserList[:idx], c.UserList[idx+1:]...)
c.ActiveUID = ""
c.activeUser = nil
if len(c.UserList) > 0 {
c.SwitchUser(c.UserList[0].UserId, "")
}
return u, nil
}
}
return nil, fmt.Errorf("未找到指定的账号")
}
// HTTPClient 返回设置好的 HTTPClient
func (c *PanConfig) HTTPClient(ua string) *requester.HTTPClient {
client := requester.NewHTTPClient()
if ua != "" {
client.SetUserAgent(ua)
}
return client
}
func (c *PanConfig) GetVideoExtensionList() []string {
if c.VideoFileExtensions == "" {
return []string{}
}
exts := strings.Split(c.VideoFileExtensions, ",")
r := []string{}
for _, ex := range exts {
ex = strings.TrimSpace(ex)
if ex != "" {
r = append(r, strings.ToLower(ex))
}
}
return r
}