aliyunpan/internal/config/pan_user.go

405 lines
11 KiB
Go

// 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"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan-api/aliyunpan/apierror"
"github.com/tickstep/aliyunpan-api/aliyunpan_open"
"github.com/tickstep/aliyunpan-api/aliyunpan_open/openapi"
"github.com/tickstep/aliyunpan-api/aliyunpan_web"
"github.com/tickstep/aliyunpan/internal/functions/panlogin"
"github.com/tickstep/library-go/expires/cachemap"
"github.com/tickstep/library-go/logger"
"path"
"path/filepath"
"time"
)
type DriveInfo struct {
DriveId string `json:"driveId"`
DriveName string `json:"driveName"`
DriveTag string `json:"driveTag"`
}
type DriveInfoList []*DriveInfo
func (d DriveInfoList) GetFileDriveId() string {
for _, drive := range d {
if drive.DriveTag == "File" {
return drive.DriveId
}
}
return ""
}
func (d DriveInfoList) GetAlbumDriveId() string {
for _, drive := range d {
if drive.DriveTag == "Album" {
return drive.DriveId
}
}
return ""
}
func (d DriveInfoList) GetResourceDriveId() string {
for _, drive := range d {
if drive.DriveTag == "Resource" {
return drive.DriveId
}
}
return ""
}
func (d DriveInfoList) GetDriveIdByName(name string) string {
for _, drive := range d {
if drive.DriveTag == name {
return drive.DriveId
}
}
return ""
}
// PanClientToken 授权Token
type PanClientToken struct {
// AccessToken AccessToken
AccessToken string `json:"accessToken"`
// Expired 过期时间戳,单位秒
Expired int64 `json:"expired"`
}
// GetExpiredTimeCstStr 获取东八区时间字符串
func (t *PanClientToken) GetExpiredTimeCstStr() string {
cz := time.FixedZone("CST", 8*3600) // 东8区
tm := time.Unix(t.Expired, 0).In(cz)
return tm.Format("2006-01-02 15:04:05")
}
type PanUser struct {
// 用户信息
UserId string `json:"userId"`
Nickname string `json:"nickname"`
AccountName string `json:"accountName"`
// 文件(备份盘)
Workdir string `json:"workdir"`
WorkdirFileEntity aliyunpan.FileEntity `json:"workdirFileEntity"`
// 资源库
ResourceWorkdir string `json:"resourceWorkdir"`
ResourceWorkdirFileEntity aliyunpan.FileEntity `json:"resourceWorkdirFileEntity"`
// 相册
AlbumWorkdir string `json:"albumWorkdir"`
AlbumWorkdirFileEntity aliyunpan.FileEntity `json:"albumWorkdirFileEntity"`
// 用户网盘信息
ActiveDriveId string `json:"activeDriveId"`
DriveList DriveInfoList `json:"driveList"`
// 授权Token信息
TicketId string `json:"ticketId"`
WebapiToken *PanClientToken `json:"webapiToken"`
OpenapiToken *PanClientToken `json:"openapiToken"`
// API客户端
panClient *PanClient `json:"-"`
cacheOpMap cachemap.CacheOpMap `json:"-"`
}
type PanUserList []*PanUser
func SetupUserByCookie(openapiToken, webapiToken *PanClientToken, ticketId, userId, deviceId, deviceName, clientId, clientSecret string) (user *PanUser, err *apierror.ApiError) {
tryRefreshWebToken := true
tryRefreshOpenToken := true
loginHelper := panlogin.NewLoginHelper(DefaultTokenServiceWebHost)
if openapiToken == nil {
return nil, apierror.NewFailedApiError("openapi token is empty")
}
//if webapiToken == nil {
// return nil, apierror.NewFailedApiError("webapi token is empty")
//}
doOpenLoginAct:
// setup openapi client
openPanClient := aliyunpan_open.NewOpenPanClient(openapi.ApiConfig{
TicketId: ticketId,
UserId: userId,
ClientId: clientId,
ClientSecret: clientSecret,
}, openapi.ApiToken{
AccessToken: openapiToken.AccessToken,
ExpiredAt: openapiToken.Expired,
}, nil)
// open api token maybe expired
// check & refresh new one
openUserInfo, err := openPanClient.GetUserInfo()
if err != nil {
if err.Code == apierror.ApiCodeTokenExpiredCode && tryRefreshOpenToken {
tryRefreshOpenToken = false
wt, e := loginHelper.GetOpenapiNewToken(ticketId, userId, openapiToken.AccessToken)
if e != nil {
logger.Verboseln("get openapi token from server error: ", e)
return nil, apierror.NewFailedApiError("get new openapi token error, try login again")
}
if wt != nil {
openapiToken = &PanClientToken{
AccessToken: wt.AccessToken,
Expired: wt.Expired,
}
}
time.Sleep(time.Duration(1) * time.Second)
goto doOpenLoginAct
}
return nil, err
}
doWebLoginAct:
// setup webapi client
var webUserInfo *aliyunpan.UserInfo
var err2 *apierror.ApiError
var webPanClient *aliyunpan_web.WebPanClient
if webapiToken != nil && webapiToken.AccessToken != "" {
appConfig := aliyunpan_web.AppConfig{
AppId: "25dzX3vbYqktVxyX",
DeviceId: deviceId,
UserId: userId,
Nonce: 0,
PublicKey: "",
}
webPanClient = aliyunpan_web.NewWebPanClient(aliyunpan_web.WebLoginToken{
AccessTokenType: "Bearer",
AccessToken: webapiToken.AccessToken,
RefreshToken: "",
ExpiresIn: 7200,
ExpireTime: webapiToken.GetExpiredTimeCstStr(),
}, aliyunpan_web.AppLoginToken{}, appConfig, aliyunpan_web.SessionConfig{
DeviceName: deviceName,
ModelName: "Windows网页版",
})
// web api token maybe expired
// check & refresh new one
webUserInfo, err2 = webPanClient.GetUserInfo()
if err2 != nil {
if err2.Code == apierror.ApiCodeTokenExpiredCode && tryRefreshWebToken {
tryRefreshWebToken = false
wt, e := loginHelper.GetWebapiNewToken(ticketId, userId, webapiToken.AccessToken)
if e != nil {
logger.Verboseln("get web token from server error: ", e)
}
if wt != nil {
webapiToken = &PanClientToken{
AccessToken: wt.AccessToken,
Expired: wt.Expired,
}
}
time.Sleep(time.Duration(1) * time.Second)
goto doWebLoginAct
}
webPanClient = nil
//return nil, err2
}
// web create session
if webUserInfo != nil {
appConfig.UserId = webUserInfo.UserId
webPanClient.UpdateAppConfig(appConfig)
r, e := webPanClient.CreateSession(nil)
if e != nil {
logger.Verboseln("call CreateSession error in SetupUserByCookie: " + e.Error())
}
if r != nil && !r.Result {
logger.Verboseln("上传签名秘钥失败,可能是你账号登录的设备已超最大数量")
}
}
}
//
// setup PanUser
//
u := &PanUser{
WebapiToken: webapiToken,
OpenapiToken: openapiToken,
panClient: NewPanClient(webPanClient, openPanClient),
Workdir: "/",
WorkdirFileEntity: *aliyunpan.NewFileEntityForRootDir(),
}
u.PanClient().OpenapiPanClient().SetAccessTokenRefreshCallback(func(userId string, newToken openapi.ApiToken) error {
logger.Verboseln("openapi token refresh, update for user")
u.OpenapiToken = &PanClientToken{
AccessToken: newToken.AccessToken,
Expired: newToken.ExpiredAt,
}
return nil
})
// setup user info
name := "Unknown"
if openUserInfo != nil {
// fill userId for client
u.PanClient().OpenapiPanClient().UpdateUserId(openUserInfo.UserId)
if u.PanClient().WebapiPanClient() != nil {
u.PanClient().WebapiPanClient().UpdateUserId(openUserInfo.UserId)
}
// update user
if openUserInfo.Nickname != "" {
name = openUserInfo.Nickname
}
u.UserId = openUserInfo.UserId
u.Nickname = name
u.AccountName = openUserInfo.UserName
// default file drive
if u.ActiveDriveId == "" {
u.ActiveDriveId = openUserInfo.FileDriveId
}
// drive list
u.DriveList = DriveInfoList{
{DriveId: openUserInfo.FileDriveId, DriveTag: "File", DriveName: "备份盘"},
{DriveId: openUserInfo.ResourceDriveId, DriveTag: "Resource", DriveName: "资源库"},
}
if webUserInfo != nil {
u.AccountName = webUserInfo.UserName
u.DriveList = append(u.DriveList, &DriveInfo{DriveId: webUserInfo.AlbumDriveId, DriveTag: "Album", DriveName: "相册"})
}
} else {
// error, maybe the token has expired
return nil, apierror.NewFailedApiError("cannot get user info, the token has expired. please login again")
}
// return user
return u, nil
}
func (pu *PanUser) PanClient() *PanClient {
return pu.panClient
}
func (pu *PanUser) UpdateClient(openClient *aliyunpan_open.OpenPanClient, webClient *aliyunpan_web.WebPanClient) {
if pu.panClient == nil {
pu.panClient = &PanClient{
webapiPanClient: webClient,
openapiPanClient: openClient,
}
} else {
pu.panClient.webapiPanClient = webClient
pu.panClient.openapiPanClient = openClient
}
}
// PathJoin 合并工作目录和相对路径p, 若p为绝对路径则忽略
func (pu *PanUser) PathJoin(driveId, p string) string {
if path.IsAbs(p) {
return p
}
wd := "/"
di := pu.GetDriveById(driveId)
if di != nil {
if di.IsFileDrive() {
wd = pu.Workdir
} else if di.IsResourceDrive() {
wd = pu.ResourceWorkdir
} else if di.IsAlbumDrive() {
wd = pu.AlbumWorkdir
}
}
return path.Join(wd, p)
}
func (pu *PanUser) FreshWorkdirInfo() {
if pu.IsFileDriveActive() {
fe, err := pu.PanClient().OpenapiPanClient().FileInfoById(pu.ActiveDriveId, pu.WorkdirFileEntity.FileId)
if err != nil {
logger.Verboseln("刷新工作目录信息失败")
return
}
pu.WorkdirFileEntity = *fe
} else if pu.IsAlbumDriveActive() {
fe, err := pu.PanClient().OpenapiPanClient().FileInfoById(pu.ActiveDriveId, pu.AlbumWorkdirFileEntity.FileId)
if err != nil {
logger.Verboseln("刷新工作目录信息失败")
return
}
pu.AlbumWorkdirFileEntity = *fe
}
}
// GetSavePath 根据提供的网盘文件路径 panpath, 返回本地储存路径,
// 返回绝对路径, 获取绝对路径出错时才返回相对路径...
func (pu *PanUser) GetSavePath(filePanPath string) string {
dirStr := filepath.Join(GetDownloadDir(), fmt.Sprintf("%s", pu.UserId), filePanPath)
dir, err := filepath.Abs(dirStr)
if err != nil {
dir = filepath.Clean(dirStr)
}
return dir
}
func (pu *PanUser) GetDriveByTag(tag string) *DriveInfo {
for _, item := range pu.DriveList {
if item.DriveTag == tag {
return item
}
}
return nil
}
func (pu *PanUser) GetDriveById(id string) *DriveInfo {
for _, item := range pu.DriveList {
if item.DriveId == id {
return item
}
}
return nil
}
func (pu *PanUser) GetActiveDriveInfo() *DriveInfo {
for _, item := range pu.DriveList {
if item.DriveId == pu.ActiveDriveId {
return item
}
}
return nil
}
func (pu *PanUser) IsFileDriveActive() bool {
d := pu.GetActiveDriveInfo()
return d != nil && d.IsFileDrive()
}
func (pu *PanUser) IsAlbumDriveActive() bool {
d := pu.GetActiveDriveInfo()
return d != nil && d.IsAlbumDrive()
}
func (pu *PanUser) IsResourceDriveActive() bool {
d := pu.GetActiveDriveInfo()
return d != nil && d.IsResourceDrive()
}
func (di *DriveInfo) IsFileDrive() bool {
return di.DriveTag == "File"
}
func (di *DriveInfo) IsAlbumDrive() bool {
return di.DriveTag == "Album"
}
func (di *DriveInfo) IsResourceDrive() bool {
return di.DriveTag == "Resource"
}