diff --git a/src/client/web/src/client/files.ts b/src/client/web/src/client/files.ts index 1054b90..4c72647 100644 --- a/src/client/web/src/client/files.ts +++ b/src/client/web/src/client/files.ts @@ -7,10 +7,12 @@ import { ListUploadingsResp, ListSharingsResp, ListSharingIDsResp, + GetSharingDirResp, } from "./"; const filePathQuery = "fp"; const listDirQuery = "dp"; +const shareIDQuery = "sh"; // TODO: get timeout from server function translateResp(resp: Response): Response { @@ -208,6 +210,16 @@ export class FilesClient extends BaseClient { }); }; + getSharingDir = (shareID: string): Promise> => { + return this.do({ + method: "get", + url: `${this.url}/v1/fs/sharings/dirs`, + params: { + [shareIDQuery]: shareID, + }, + }); + }; + generateHash = (filePath: string): Promise => { return this.do({ method: "post", diff --git a/src/client/web/src/client/files_mock.ts b/src/client/web/src/client/files_mock.ts index b6630da..a5c373e 100644 --- a/src/client/web/src/client/files_mock.ts +++ b/src/client/web/src/client/files_mock.ts @@ -5,6 +5,7 @@ import { ListUploadingsResp, ListSharingsResp, ListSharingIDsResp, + GetSharingDirResp, } from "./"; export interface FilesClientResps { @@ -26,6 +27,7 @@ export interface FilesClientResps { deleteSharingMockResp?: Response; listSharingsMockResp?: Response; listSharingIDsMockResp?: Response; + getSharingDirMockResp?: Response; isSharingMockResp?: Response; generateHashMockResp?: Response; downloadMockResp: Response; @@ -144,6 +146,13 @@ export const resps = { IDs: sharingIDs, }, }, + getSharingDirMockResp: { + status: 200, + statusText: "", + data: { + sharingDir: "/admin/sharing", + }, + }, isSharingMockResp: { status: 200, statusText: "", data: {} }, generateHashMockResp: { status: 200, statusText: "", data: {} }, downloadMockResp: { @@ -231,10 +240,15 @@ export class MockFilesClient { listSharings = (): Promise> => { return this.wrapPromise(this.resps.listSharingsMockResp); }; + listSharingIDs = (): Promise> => { return this.wrapPromise(this.resps.listSharingIDsMockResp); }; + getSharingDir = (): Promise> => { + return this.wrapPromise(this.resps.getSharingDirMockResp); + } + isSharing = (dirPath: string): Promise => { return this.wrapPromise(this.resps.isSharingMockResp); }; diff --git a/src/client/web/src/client/index.ts b/src/client/web/src/client/index.ts index 66b70f9..140a9b6 100644 --- a/src/client/web/src/client/index.ts +++ b/src/client/web/src/client/index.ts @@ -84,6 +84,10 @@ export interface ListSharingIDsResp { IDs: Map; } +export interface GetSharingDirResp { + sharingDir: string; +} + export interface ClientConfigMsg { clientCfg: ClientConfig; } @@ -142,6 +146,7 @@ export interface IFilesClient { isSharing: (dirPath: string) => Promise; listSharings: () => Promise>; listSharingIDs: () => Promise>; + getSharingDir: (shareID: string) => Promise>; generateHash: (filePath: string) => Promise; download: (url: string) => Promise; } diff --git a/src/client/web/src/components/state_updater.ts b/src/client/web/src/components/state_updater.ts index 9f946d3..7974749 100644 --- a/src/client/web/src/components/state_updater.ts +++ b/src/client/web/src/components/state_updater.ts @@ -127,13 +127,7 @@ export class Updater { if (resp.status !== 200) { return errServer; } - - // transform from built-in map to immutable map - let sharings = Map(); - resp.data.IDs.forEach((shareID: string, dirPath: string) => { - sharings = sharings.set(dirPath, shareID); - }); - this.props.sharingsInfo.sharings = sharings; + this.props.sharingsInfo.sharings = Map(resp.data.IDs); return ""; }; @@ -464,8 +458,28 @@ export class Updater { return ""; }; + getParamMap = async (params: URLSearchParams): Promise> => { + let paramMap = Map(); + paramMap = paramMap.set("sh", ""); + paramMap = paramMap.set("dir", ""); + + let shareID = params.get("sh"); + if (shareID != null && shareID !== "") { + paramMap = paramMap.set("sh", shareID); + const resp = await this.filesClient.getSharingDir(shareID) + if (resp.status === 200) { + paramMap = paramMap.set("dir", resp.data.sharingDir); + } + } else { + paramMap = paramMap.set("dir", params.get("dir")); + } + + return paramMap; + }; + initCwd = async (params: URLSearchParams): Promise => { - const dir = params.get("dir"); + const paramMap = await this.getParamMap(params) + const dir = paramMap.get("dir", ""); if (dir != null && dir !== "") { // in sharing mode diff --git a/src/db/fileinfostore/file_info_store.go b/src/db/fileinfostore/file_info_store.go index ecdc6ac..775fc30 100644 --- a/src/db/fileinfostore/file_info_store.go +++ b/src/db/fileinfostore/file_info_store.go @@ -8,16 +8,17 @@ import ( "io" "strings" "sync" - "time" "github.com/ihexxa/quickshare/src/kvstore" ) const ( - InitNs = "Init" - InfoNs = "sharing" - ShareIDNs = "sharingKey" - InitTimeKey = "initTime" + InitNs = "Init" + InfoNs = "sharing" + ShareIDNs = "sharingKey" + InitTimeKey = "initTime" + SchemaVerKey = "SchemaVersion" + SchemaV1 = "v1" ) var ( @@ -25,6 +26,7 @@ var ( ErrNotFound = errors.New("file info not found") ErrSharingNotFound = errors.New("sharing id not found") ErrConflicted = errors.New("conflict found in hashing") + ErrVerNotFound = errors.New("file info schema version not found") maxHashingTime = 10 ) @@ -57,30 +59,99 @@ type FileInfoStore struct { store kvstore.IKVStore } +func migrate(fi *FileInfoStore) error { + ver := "v0" + schemaVer, ok := fi.store.GetStringIn(InitNs, SchemaVerKey) + if ok { + ver = schemaVer + } + + switch ver { + case "v0": + // add ShareID to FileInfos + infoStrs, err := fi.store.ListStringsIn(InfoNs) + if err != nil { + return err + } + + type FileInfoV0 struct { + IsDir bool `json:"isDir"` + Shared bool `json:"shared"` + Sha1 string `json:"sha1"` + } + + infoV0 := &FileInfoV0{} + for itemPath, infoStr := range infoStrs { + err = json.Unmarshal([]byte(infoStr), infoV0) + if err != nil { + return fmt.Errorf("list sharing error: %w", err) + } + + shareID := "" + if infoV0.IsDir && infoV0.Shared { + dirShareID, err := fi.getShareID(itemPath) + if err != nil { + return err + } + shareID = dirShareID + + err = fi.store.SetStringIn(ShareIDNs, shareID, itemPath) + if err != nil { + return err + } + } + + newInfo := &FileInfo{ + IsDir: infoV0.IsDir, + Shared: infoV0.Shared, + ShareID: shareID, + Sha1: infoV0.Sha1, + } + if err = fi.SetInfo(itemPath, newInfo); err != nil { + return err + } + } + + err = fi.store.SetStringIn(InitNs, SchemaVerKey, SchemaV1) + if err != nil { + return err + } + case "v1": + // no op + default: + return fmt.Errorf("file info: unknown schema version (%s)", ver) + } + + return nil +} + func NewFileInfoStore(store kvstore.IKVStore) (*FileInfoStore, error) { - _, ok := store.GetStringIn(InitNs, InitTimeKey) - if !ok { - var err error - for _, nsName := range []string{ - InitNs, - InfoNs, - ShareIDNs, - } { + var err error + for _, nsName := range []string{ + InitNs, + InfoNs, + ShareIDNs, + } { + if !store.HasNamespace(nsName) { if err = store.AddNamespace(nsName); err != nil { return nil, err } } } - err := store.SetStringIn(InitNs, InitTimeKey, fmt.Sprintf("%d", time.Now().Unix())) - if err != nil { - return nil, err - } + // err = store.SetStringIn(InitNs, SchemaVerKey, SchemaV1) + // if err != nil { + // return nil, err + // } - return &FileInfoStore{ + fi := &FileInfoStore{ store: store, mtx: &sync.RWMutex{}, - }, nil + } + if err = migrate(fi); err != nil { + return nil, err + } + return fi, nil } func (fi *FileInfoStore) AddSharing(dirPath string) error { @@ -159,6 +230,8 @@ func (fi *FileInfoStore) ListSharings(prefix string) (map[string]string, error) if err != nil { return nil, fmt.Errorf("list sharing error: %w", err) } + + fmt.Println(infoStr) if info.IsDir && info.Shared { sharings[itemPath] = info.ShareID }