Skip to content

Commit e09a737

Browse files
committed
-serialized storage backend access
-better environmentization -bug fixes
1 parent 1b21915 commit e09a737

14 files changed

+208
-47
lines changed

portal/src/FileEventsService.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* OpenDistributedFileStorage
3+
* Copyright (C) 2024 Amir Czwink ([email protected])
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
* */
18+
19+
import { Injectable } from "acfrontend";
20+
import { Subject } from "acts-util-core";
21+
22+
@Injectable
23+
export class FileEventsService
24+
{
25+
constructor()
26+
{
27+
this.onChanged = new Subject();
28+
}
29+
30+
public onChanged: Subject<void>;
31+
}

portal/src/file-explorer/DirectoryViewComponent.tsx

Lines changed: 15 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,20 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
* */
1818

19-
import { Anchor, AutoCompleteMultiSelectBox, BootstrapIcon, Component, FormField, Injectable, JSX_CreateElement, JSX_Fragment, LineEdit, ProgressSpinner, RouteParamProperty, Router } from "acfrontend";
19+
import { Anchor, AutoCompleteMultiSelectBox, BootstrapIcon, Component, FormField, Injectable, JSX_CreateElement, JSX_Fragment, LineEdit, ProgressSpinner, RouteParamProperty } from "acfrontend";
2020
import { APIService } from "../APIService";
2121
import { DirectoryContentsDTO } from "../../dist/api";
2222
import { FilesGridView } from "./FilesGridView";
2323
import { FilesTableView } from "./FilesTableView";
2424

2525
@Injectable
26-
export class DirectoryViewComponent extends Component
26+
export class DirectoryViewComponent extends Component<{ dirPath: string }>
2727
{
28-
constructor(private apiService: APIService, @RouteParamProperty("containerId") private containerId: number, private router: Router)
28+
constructor(private apiService: APIService, @RouteParamProperty("containerId") private containerId: number)
2929
{
3030
super();
3131

3232
this.containerName = "";
33-
this.dirPath = "/";
3433
this.data = null;
3534
this.view = "grid";
3635
this.showSearch = false;
@@ -62,17 +61,6 @@ export class DirectoryViewComponent extends Component
6261
}
6362

6463
//Private methods
65-
private ExtractDirPath()
66-
{
67-
const dirPath = this.router.state.Get().queryParams.dirPath;
68-
if(dirPath !== undefined)
69-
this.dirPath = decodeURIComponent(dirPath);
70-
else
71-
this.dirPath = "/";
72-
73-
this.LoadData();
74-
}
75-
7664
private GetToggleButtonClassName(condition: boolean)
7765
{
7866
const className = condition ? "btn-primary btn-active" : "text-primary";
@@ -82,7 +70,7 @@ export class DirectoryViewComponent extends Component
8270
private async LoadData()
8371
{
8472
this.data = null;
85-
const response = await this.apiService.containers._any_.files.get(this.containerId, { dirPath: this.dirPath });
73+
const response = await this.apiService.containers._any_.files.get(this.containerId, { dirPath: this.input.dirPath });
8674
if(response.statusCode !== 200)
8775
throw new Error("TODO: implement me");
8876
this.data = response.data;
@@ -94,8 +82,8 @@ export class DirectoryViewComponent extends Component
9482
return <ProgressSpinner />;
9583

9684
if(this.view === "grid")
97-
return <FilesGridView containerId={this.containerId} contents={this.data} parentPath={this.dirPath} />;
98-
return <FilesTableView containerId={this.containerId} contents={this.data} parentPath={this.dirPath} />;
85+
return <FilesGridView containerId={this.containerId} contents={this.data} parentPath={this.input.dirPath} />;
86+
return <FilesTableView containerId={this.containerId} contents={this.data} parentPath={this.input.dirPath} />;
9987
}
10088

10189
private RenderNav()
@@ -105,9 +93,9 @@ export class DirectoryViewComponent extends Component
10593
return "/" + dirs.slice(0, idx + 1).join("/");
10694
}
10795

108-
if(this.dirPath === "/")
96+
if(this.input.dirPath === "/")
10997
return <li className="breadcrumb-item active">{this.containerName}</li>;
110-
const dirs = this.dirPath.substring(1).split("/");
98+
const dirs = this.input.dirPath.substring(1).split("/");
11199
const last = dirs.pop();
112100
return <>
113101
<li className="breadcrumb-item"><Anchor route={"/" + this.containerId}>{this.containerName}</Anchor></li>
@@ -157,8 +145,12 @@ export class DirectoryViewComponent extends Component
157145
const response = await this.apiService.containers.get();
158146
this.containerName = response.data.find(x => x.id === this.containerId)!.name;
159147

160-
this.ExtractDirPath();
161-
this.router.state.Subscribe(this.ExtractDirPath.bind(this));
148+
this.LoadData();
149+
}
150+
151+
override OnInputChanged(): void
152+
{
153+
this.LoadData();
162154
}
163155

164156
private async OnLoadTags(searchText: string)
@@ -176,7 +168,7 @@ export class DirectoryViewComponent extends Component
176168

177169
this.data = null;
178170
const response = await this.apiService.containers._any_.search.get(this.containerId, {
179-
dirPath: this.dirPath,
171+
dirPath: this.input.dirPath,
180172
nameFilter: this.searchFilterName,
181173
mediaTypeFilter: this.searchFilterMediaType,
182174
requiredTags: this.searchTags.join(",")
@@ -197,7 +189,6 @@ export class DirectoryViewComponent extends Component
197189

198190
//State
199191
private containerName: string;
200-
private dirPath: string;
201192
private data: DirectoryContentsDTO | null;
202193
private view: "grid" | "list";
203194
private showSearch: boolean;

portal/src/file-explorer/EditFileAttributesComponent.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import { AutoCompleteTextLineEdit, BootstrapIcon, FormField, JSX_CreateElement, LineEdit, PushButton, Router, Use, UseAPI, UseDeferredAPI, UseRouteParameter, UseState } from "acfrontend";
2020
import { APIService } from "../APIService";
2121
import { FileMetaDataDTO } from "../../dist/api";
22+
import { FileEventsService } from "../FileEventsService";
2223

2324
async function LoadTags(containerId: number, searchText: string)
2425
{
@@ -54,7 +55,10 @@ function FileEditor(input: { containerId: number; fileId: number; fileData: File
5455
filePath: state.filePath,
5556
tags: state.tags
5657
}),
57-
() => Use(Router).RouteTo("/" + input.containerId + "/" + input.fileId)
58+
() => {
59+
Use(FileEventsService).onChanged.Next();
60+
Use(Router).RouteTo("/" + input.containerId + "/" + input.fileId);
61+
}
5862
);
5963
if(apiState.started)
6064
return apiState.fallback;

portal/src/file-explorer/FileExplorerComponent.tsx

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class FileExplorerComponent extends Component
3131
super();
3232

3333
this.loading = false;
34+
this.dirPath = "/";
3435
}
3536

3637
protected Render(): RenderValue
@@ -39,18 +40,30 @@ export class FileExplorerComponent extends Component
3940
return <ProgressSpinner />;
4041

4142
if(this.apiService.readOnly)
42-
return <DirectoryViewComponent />;
43+
return <DirectoryViewComponent dirPath={this.dirPath} />;
4344
return <div ondragenter={this.OnDragEnter.bind(this)} ondragleave={this.OnDragLeave.bind(this)} ondragover={this.OnDragOver.bind(this)} ondrop={this.OnDrop.bind(this)} style="min-height: 95vh">
44-
<DirectoryViewComponent />
45+
<DirectoryViewComponent dirPath={this.dirPath} />
4546
</div>;
4647
}
4748

4849
//Private methods
50+
private ExtractDirPath()
51+
{
52+
const dirPath = this.router.state.Get().queryParams.dirPath;
53+
if(dirPath !== undefined)
54+
this.dirPath = decodeURIComponent(dirPath);
55+
else
56+
this.dirPath = "/";
57+
}
58+
4959
private async UploadFile(file: File)
5060
{
61+
console.log("Uploading file", file.name);
5162
const response = await this.apiService.containers._any_.files.post(this.containerId, {
63+
parentPath: this.dirPath,
5264
file
5365
});
66+
console.log("Finished", file.name, "result: ", response);
5467
switch(response.statusCode)
5568
{
5669
case 200:
@@ -150,6 +163,13 @@ export class FileExplorerComponent extends Component
150163
await this.UploadFiles(files);
151164
}
152165

166+
override OnInitiated(): void
167+
{
168+
this.ExtractDirPath();
169+
this.router.state.Subscribe(this.ExtractDirPath.bind(this));
170+
}
171+
153172
//State
173+
private dirPath: string;
154174
private loading: boolean;
155175
}

portal/src/file-explorer/ViewFileComponent.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import { Injectable, Component, RouteParamProperty, ProgressSpinner, JSX_CreateElement, JSX_Fragment, BootstrapIcon, Anchor, FileDownloadService, NavItem, RouterComponent, InfoMessageManager } from "acfrontend";
2020
import { APIService } from "../APIService";
2121
import { FileMetaDataDTO } from "../../dist/api";
22+
import { FileEventsService } from "../FileEventsService";
23+
import { Subscription } from "acts-util-core";
2224

2325
let dragCounter = 0;
2426

@@ -28,6 +30,7 @@ export class ViewFileComponent extends Component
2830
constructor(private apiService: APIService, private fileDownloadService: FileDownloadService, private infoMessageManager: InfoMessageManager,
2931
@RouteParamProperty("containerId") private containerId: number,
3032
@RouteParamProperty("fileId") private fileId: number,
33+
private fileEventsService: FileEventsService
3134
)
3235
{
3336
super();
@@ -225,9 +228,19 @@ export class ViewFileComponent extends Component
225228
throw new Error("TODO: implement me");
226229
this.containerName = response.data.name;
227230
this.LoadData();
231+
232+
this.subscription = this.fileEventsService.onChanged.Subscribe({
233+
next: () => this.LoadData()
234+
});
235+
}
236+
237+
override OnUnmounted(): void
238+
{
239+
this.subscription?.Unsubscribe();
228240
}
229241

230242
//State
231243
private data: FileMetaDataDTO | null;
232244
private containerName: string;
245+
private subscription?: Subscription;
233246
}

service/src/api/containers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,11 @@ class _api3_
133133
@Security(OIDC_API_SCHEME, [SCOPE_FILES_WRITE])
134134
public async Create(
135135
@Path containerId: number,
136+
@FormField parentPath: string,
136137
@FormField file: UploadedFile
137138
)
138139
{
139-
const result = await this.fileUploadService.Upload(containerId, file.originalName, file.mediaType, file.buffer);
140+
const result = await this.fileUploadService.Upload(containerId, parentPath, file.originalName, file.mediaType, file.buffer);
140141
if(result === "error_file_exists")
141142
return Conflict("file exists already");
142143
return result;

service/src/data-access/FilesController.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,12 +45,12 @@ export class FilesController
4545
}
4646

4747
//Public methods
48-
public async AddFile(containerId: number, originalName: string, mediaType: string)
48+
public async AddFile(containerId: number, filePath: string, mediaType: string)
4949
{
5050
const conn = await this.dbConnMgr.CreateAnyConnectionQueryExecutor();
5151
const result = await conn.InsertRow("files", {
5252
containerId,
53-
filePath: "/" + originalName,
53+
filePath,
5454
mediaType,
5555
});
5656

service/src/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const CONFIG_DB = {
2222
password: process.env.ODFS_DBPW!,
2323
};
2424

25+
export const CONFIG_MAX_NUMBER_OF_CACHED_BLOCKS = parseInt(process.env.ODFS_MAX_CACHE_SIZE!); //in units of 100 MiB
2526
export const CONFIG_OIDP_ENDPOINT = process.env.ODFS_OIDP_ENDPOINT!;
2627
export const CONFIG_ORIGIN = process.env.ODFS_ORIGIN!;
2728
export const CONFIG_PORT = process.env.ODFS_PORT;

service/src/services/FFProbeService.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,15 @@ export class FFProbeService
9393
switch(stream.codec_type)
9494
{
9595
case "audio":
96-
return stream.codec_name === "mp3";
96+
{
97+
switch(stream.codec_name)
98+
{
99+
case "aac":
100+
case "mp3":
101+
return true;
102+
}
103+
return false;
104+
}
97105
case "video":
98106
return stream.codec_name === "h264";
99107
}

service/src/services/FileUploadService.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1717
* */
1818
import crypto from "crypto";
19+
import path from "path";
1920
import { Readable } from 'stream';
2021
import { Injectable } from "acts-util-node";
2122
import { BlobsController } from "../data-access/BlobsController";
@@ -34,13 +35,14 @@ export class FileUploadService
3435
}
3536

3637
//Public methods
37-
public async Upload(containerId: number, originalName: string, mediaType: string, buffer: Buffer)
38+
public async Upload(containerId: number, parentPath: string, originalName: string, mediaType: string, buffer: Buffer)
3839
{
3940
const blobId = await this.ProcessBlob(Readable.from(buffer));
4041
let fileId;
4142
try
4243
{
43-
fileId = await this.filesController.AddFile(containerId, originalName, mediaType);
44+
const filePath = path.join(parentPath, originalName);
45+
fileId = await this.filesController.AddFile(containerId, filePath, mediaType);
4446
}
4547
catch(e: any)
4648
{
@@ -148,28 +150,21 @@ export class FileUploadService
148150
{
149151
const blockId = await this.blobsController.FindBlobBlock(blobBlock.byteLength, sha256sum);
150152
if(blockId !== undefined)
151-
{
152-
console.log("found");
153153
return blockId;
154-
}
155154

156155
let newBlockId;
157156
try
158157
{
159158
newBlockId = await this.blobsController.AddBlobBlock(blobBlock.byteLength, sha256sum);
160-
console.log("added blob block", newBlockId, blobBlock.byteLength, sha256sum);
161159
}
162160
catch(e: any)
163161
{
164-
console.log("error", e);
165162
if(e?.code === "ER_DUP_ENTRY")
166163
return await this.ProcessBlobBlockHashed(blobBlock, sha256sum);
167164
throw e;
168165
}
169-
console.log("adding", newBlockId);
170166
const storageBlock = await this.storageBackendsManager.StoreBlobBlock(blobBlock);
171167
await this.blobsController.AddBlobBlockStorage(newBlockId, storageBlock.id, storageBlock.offset);
172-
console.log("added", newBlockId, storageBlock.id, storageBlock.offset);
173168

174169
return newBlockId;
175170
}

service/src/services/StorageBackendsManager.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { Dictionary, ObjectExtensions } from "acts-util-core";
2525
import { StorageBlocksController } from "../data-access/StorageBlocksController";
2626
import { CONST_STORAGEBLOCKS_MAX_REPLICATION, StorageTier } from "../constants";
2727
import { WebDAVBackend } from "../storage-backends/WebDAVBackend";
28+
import { LockedStorageBackend } from "../storage-backends/LockedStorageBackend";
2829

2930
export interface StorageBackendCreationData
3031
{
@@ -100,6 +101,11 @@ export class StorageBackendsManager
100101

101102
//Private methods
102103
private CreateBackendInstance(config: StorageBackendConfig): StorageBackend
104+
{
105+
return new LockedStorageBackend(this.CreateRealBackendInstance(config));
106+
}
107+
108+
private CreateRealBackendInstance(config: StorageBackendConfig): StorageBackend
103109
{
104110
switch(config.type)
105111
{

0 commit comments

Comments
 (0)