Merge pull request #7 from Trivernis/feature/performance-improvements

Feature/performance improvements
pull/8/head
Julius Riegel 3 years ago committed by GitHub
commit 53fbf2f6b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
[package] [package]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.27.0" version = "0.28.0"
edition = "2018" edition = "2018"
license = "gpl-3" license = "gpl-3"

@ -1,6 +1,7 @@
use std::collections::HashMap;
use crate::client_api::error::ApiResult; use crate::client_api::error::ApiResult;
use crate::client_api::IPCApi; use crate::client_api::IPCApi;
use crate::types::files::{GetFileTagsRequest, GetFilesTagsRequest}; use crate::types::files::{GetFileTagsRequest, GetFilesTagsRequest, GetFileTagMapRequest};
use crate::types::identifier::FileIdentifier; use crate::types::identifier::FileIdentifier;
use crate::types::tags::{ChangeFileTagsRequest, NamespaceResponse, TagResponse}; use crate::types::tags::{ChangeFileTagsRequest, NamespaceResponse, TagResponse};
use async_trait::async_trait; use async_trait::async_trait;
@ -72,6 +73,12 @@ impl TagApi {
.await .await
} }
/// Returns a map from files to assigned tags
#[tracing::instrument(level = "debug", skip_all)]
pub async fn get_file_tag_map(&self, cds: Vec<String>) -> ApiResult<HashMap<String, Vec<TagResponse>>> {
self.emit_and_get("file_tag_map", GetFileTagMapRequest{cds}, Some(Duration::from_secs(10))).await
}
/// Creates a new tag and returns the created tag object /// Creates a new tag and returns the created tag object
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn create_tags(&self, tags: Vec<String>) -> ApiResult<Vec<TagResponse>> { pub async fn create_tags(&self, tags: Vec<String>) -> ApiResult<Vec<TagResponse>> {

@ -1,3 +1,4 @@
use std::collections::HashMap;
use crate::tauri_plugin::commands::ApiAccess; use crate::tauri_plugin::commands::ApiAccess;
use crate::tauri_plugin::error::PluginResult; use crate::tauri_plugin::error::PluginResult;
use crate::types::identifier::FileIdentifier; use crate::types::identifier::FileIdentifier;
@ -41,6 +42,14 @@ pub async fn get_tags_for_files(
Ok(tags) Ok(tags)
} }
#[tauri::command]
pub async fn get_file_tag_map(cds: Vec<String>, api_state: ApiAccess<'_>) -> PluginResult<HashMap<String, Vec<TagResponse>>> {
let api = api_state.api().await?;
let mappings = api.tag.get_file_tag_map(cds).await?;
Ok(mappings)
}
#[tauri::command] #[tauri::command]
pub async fn create_tags( pub async fn create_tags(
api_state: ApiAccess<'_>, api_state: ApiAccess<'_>,

@ -69,7 +69,8 @@ impl<R: Runtime> MediarepoPlugin<R> {
get_file_metadata, get_file_metadata,
run_job, run_job,
update_file_status, update_file_status,
delete_file delete_file,
get_file_tag_map
]), ]),
} }
} }

@ -30,6 +30,11 @@ pub struct GetFilesTagsRequest {
pub cds: Vec<String>, pub cds: Vec<String>,
} }
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GetFileTagMapRequest {
pub cds: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FileBasicDataResponse { pub struct FileBasicDataResponse {
pub id: i64, pub id: i64,

@ -11,4 +11,5 @@ pub enum JobType {
MigrateContentDescriptors, MigrateContentDescriptors,
CalculateSizes, CalculateSizes,
CheckIntegrity, CheckIntegrity,
Vacuum,
} }

@ -1182,7 +1182,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.27.0" version = "0.28.0"
dependencies = [ dependencies = [
"bromine", "bromine",
"chrono", "chrono",

@ -5,6 +5,7 @@ use std::collections::HashMap;
use std::iter::FromIterator; use std::iter::FromIterator;
use mediarepo_core::error::RepoResult; use mediarepo_core::error::RepoResult;
use mediarepo_core::itertools::Itertools;
use mediarepo_core::utils::parse_namespace_and_tag; use mediarepo_core::utils::parse_namespace_and_tag;
use mediarepo_database::entities::{content_descriptor, content_descriptor_tag, namespace, tag}; use mediarepo_database::entities::{content_descriptor, content_descriptor_tag, namespace, tag};
@ -57,7 +58,7 @@ impl TagDao {
Ok(namespaces) Ok(namespaces)
} }
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self, cds))]
pub async fn all_for_cds(&self, cds: Vec<Vec<u8>>) -> RepoResult<Vec<TagDto>> { pub async fn all_for_cds(&self, cds: Vec<Vec<u8>>) -> RepoResult<Vec<TagDto>> {
let tags = tag::Entity::find() let tags = tag::Entity::find()
.find_also_related(namespace::Entity) .find_also_related(namespace::Entity)
@ -80,6 +81,51 @@ impl TagDao {
Ok(tags) Ok(tags)
} }
#[tracing::instrument(level = "debug", skip(self, cds))]
pub async fn all_for_cds_map(
&self,
cds: Vec<Vec<u8>>,
) -> RepoResult<HashMap<Vec<u8>, Vec<TagDto>>> {
let tag_cd_entries: Vec<(
content_descriptor_tag::Model,
Option<content_descriptor::Model>,
)> = content_descriptor_tag::Entity::find()
.find_also_related(content_descriptor::Entity)
.filter(content_descriptor::Column::Descriptor.is_in(cds))
.all(&self.ctx.db)
.await?;
let tag_ids: Vec<i64> = tag_cd_entries
.iter()
.map(|(t, _)| t.tag_id)
.unique()
.collect();
let tags: Vec<TagDto> = tag::Entity::find()
.find_also_related(namespace::Entity)
.filter(tag::Column::Id.is_in(tag_ids))
.all(&self.ctx.db)
.await?
.into_iter()
.map(map_tag_dto)
.collect();
let tag_id_map = tags
.into_iter()
.map(|t| (t.id(), t))
.collect::<HashMap<i64, TagDto>>();
let cd_tag_map = tag_cd_entries
.into_iter()
.filter_map(|(t, cd)| Some((cd?, tag_id_map.get(&t.tag_id)?.clone())))
.sorted_by_key(|(cd, _)| cd.id)
.group_by(|(cd, _)| cd.descriptor.to_owned())
.into_iter()
.map(|(key, group)| (key, group.map(|(_, t)| t).collect::<Vec<TagDto>>()))
.collect();
Ok(cd_tag_map)
}
#[tracing::instrument(level = "debug", skip(self))] #[tracing::instrument(level = "debug", skip(self))]
pub async fn tags_for_cd(&self, cd_id: i64) -> RepoResult<Vec<TagDto>> { pub async fn tags_for_cd(&self, cd_id: i64) -> RepoResult<Vec<TagDto>> {
let tags = tag::Entity::find() let tags = tag::Entity::find()

@ -31,6 +31,7 @@ impl JobsNamespace {
JobType::MigrateContentDescriptors => job_dao.migrate_content_descriptors().await?, JobType::MigrateContentDescriptors => job_dao.migrate_content_descriptors().await?,
JobType::CalculateSizes => calculate_all_sizes(ctx).await?, JobType::CalculateSizes => calculate_all_sizes(ctx).await?,
JobType::CheckIntegrity => job_dao.check_integrity().await?, JobType::CheckIntegrity => job_dao.check_integrity().await?,
JobType::Vacuum => job_dao.vacuum().await?,
} }
ctx.emit_to(Self::name(), "run_job", ()).await?; ctx.emit_to(Self::name(), "run_job", ()).await?;

@ -1,8 +1,11 @@
use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::iter::{IntoParallelIterator, ParallelIterator};
use std::collections::HashMap;
use mediarepo_core::bromine::prelude::*; use mediarepo_core::bromine::prelude::*;
use mediarepo_core::content_descriptor::decode_content_descriptor; use mediarepo_core::content_descriptor::{decode_content_descriptor, encode_content_descriptor};
use mediarepo_core::mediarepo_api::types::files::{GetFileTagsRequest, GetFilesTagsRequest}; use mediarepo_core::mediarepo_api::types::files::{
GetFileTagMapRequest, GetFileTagsRequest, GetFilesTagsRequest,
};
use mediarepo_core::mediarepo_api::types::tags::{ use mediarepo_core::mediarepo_api::types::tags::{
ChangeFileTagsRequest, NamespaceResponse, TagResponse, ChangeFileTagsRequest, NamespaceResponse, TagResponse,
}; };
@ -26,6 +29,7 @@ impl NamespaceProvider for TagsNamespace {
"all_namespaces" => Self::all_namespaces, "all_namespaces" => Self::all_namespaces,
"tags_for_file" => Self::tags_for_file, "tags_for_file" => Self::tags_for_file,
"tags_for_files" => Self::tags_for_files, "tags_for_files" => Self::tags_for_files,
"file_tag_map" => Self::tag_cd_map_for_files,
"create_tags" => Self::create_tags, "create_tags" => Self::create_tags,
"change_file_tags" => Self::change_file_tags "change_file_tags" => Self::change_file_tags
); );
@ -105,6 +109,38 @@ impl TagsNamespace {
Ok(()) Ok(())
} }
/// Returns a map of content descriptors to assigned tags
#[tracing::instrument(skip_all)]
async fn tag_cd_map_for_files(ctx: &Context, event: Event) -> IPCResult<()> {
let request = event.payload::<GetFileTagMapRequest>()?;
let repo = get_repo_from_context(ctx).await;
let cds = request
.cds
.into_iter()
.filter_map(|c| decode_content_descriptor(c).ok())
.collect();
let mappings = repo
.tag()
.all_for_cds_map(cds)
.await?
.into_iter()
.map(|(cd, tags)| (encode_content_descriptor(&cd), tags))
.map(|(cd, tags)| {
(
cd,
tags.into_iter()
.map(TagResponse::from_model)
.collect::<Vec<TagResponse>>(),
)
})
.collect::<HashMap<String, Vec<TagResponse>>>();
ctx.emit_to(Self::name(), "file_tag_map", mappings).await?;
Ok(())
}
/// Creates all tags given as input or returns the existing tags /// Creates all tags given as input or returns the existing tags
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
async fn create_tags(ctx: &Context, event: Event) -> IPCResult<()> { async fn create_tags(ctx: &Context, event: Event) -> IPCResult<()> {

@ -1499,7 +1499,7 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
[[package]] [[package]]
name = "mediarepo-api" name = "mediarepo-api"
version = "0.27.0" version = "0.28.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bromine", "bromine",

@ -13,6 +13,7 @@ import {
DeleteThumbnailsRequest, DeleteThumbnailsRequest,
FindFilesRequest, FindFilesRequest,
GetFileMetadataRequest, GetFileMetadataRequest,
GetFileTagMapRequest,
GetSizeRequest, GetSizeRequest,
GetTagsForFilesRequest, GetTagsForFilesRequest,
InitRepositoryRequest, InitRepositoryRequest,
@ -28,7 +29,7 @@ import {
UpdateFileStatusRequest UpdateFileStatusRequest
} from "./api-types/requests"; } from "./api-types/requests";
import {RepositoryData, RepositoryMetadata, SizeMetadata} from "./api-types/repo"; import {RepositoryData, RepositoryMetadata, SizeMetadata} from "./api-types/repo";
import {NamespaceData, TagData} from "./api-types/tags"; import {CdTagMappings, NamespaceData, TagData} from "./api-types/tags";
import {ShortCache} from "./ShortCache"; import {ShortCache} from "./ShortCache";
export class MediarepoApi { export class MediarepoApi {
@ -146,6 +147,15 @@ export class MediarepoApi {
); );
} }
public static async getFileTagMap(request: GetFileTagMapRequest): Promise<CdTagMappings> {
return ShortCache.cached(
request,
() => this.invokePlugin(ApiFunction.GetFileTagMap, request),
1000,
"getFileTagMap"
);
}
public static async createTags(request: CreateTagsRequest): Promise<TagData[]> { public static async createTags(request: CreateTagsRequest): Promise<TagData[]> {
return this.invokePlugin(ApiFunction.CreateTags, request); return this.invokePlugin(ApiFunction.CreateTags, request);
} }

@ -29,6 +29,7 @@ export enum ApiFunction {
GetAllTags = "get_all_tags", GetAllTags = "get_all_tags",
GetAllNamespace = "get_all_namespaces", GetAllNamespace = "get_all_namespaces",
GetTagsForFiles = "get_tags_for_files", GetTagsForFiles = "get_tags_for_files",
GetFileTagMap = "get_file_tag_map",
CreateTags = "create_tags", CreateTags = "create_tags",
ChangeFileTags = "change_file_tags", ChangeFileTags = "change_file_tags",
// import // import

@ -1,3 +1,4 @@
export type JobType = "MigrateContentDescriptors" export type JobType = "MigrateContentDescriptors"
| "CalculateSizes" | "CalculateSizes"
| "CheckIntegrity"; | "CheckIntegrity"
| "Vacuum";

@ -73,6 +73,10 @@ export type GetTagsForFilesRequest = {
cds: string[] cds: string[]
}; };
export type GetFileTagMapRequest = {
cds: string[]
};
export type CreateTagsRequest = { export type CreateTagsRequest = {
tags: string[] tags: string[]
}; };

@ -8,3 +8,7 @@ export type NamespaceData = {
id: number, id: number,
name: string, name: string,
}; };
export type CdTagMappings = {
[key: string]: TagData[],
};

@ -14,4 +14,5 @@
::ng-deep .app-error { ::ng-deep .app-error {
background-color: $warn; background-color: $warn;
color: $text;
} }

@ -63,21 +63,21 @@ export class AppComponent implements OnInit {
if (!environment.production) { if (!environment.production) {
switch (entry.getLevel()) { switch (entry.getLevel()) {
case LogLevel.Trace: case LogLevel.Trace:
console.trace(entry.getMessage(), entry); console.trace(entry.getMessage());
break; break;
case LogLevel.Debug: case LogLevel.Debug:
console.debug(entry.getMessage(), entry); console.debug(entry.getMessage());
break; break;
case LogLevel.Info: case LogLevel.Info:
console.info(entry.getMessage(), entry); console.info(entry.getMessage());
break; break;
case LogLevel.Warn: case LogLevel.Warn:
console.warn(entry.getMessage(), entry); console.warn(entry.getMessage());
break; break;
} }
} }
if (entry.getLevel() == LogLevel.Error) { if (entry.getLevel() == LogLevel.Error) {
console.error(entry.getMessage(), entry.getError(), entry); console.error(entry.getMessage(), entry.getError());
} }
} }
} }

@ -111,13 +111,15 @@ export class RepositoriesTabComponent implements OnInit, AfterViewInit {
} }
private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise<void> { private async runRepositoryStartupTasks(dialogContext: BusyDialogContext): Promise<void> {
dialogContext.message.next("Checking integrity...");
await this.jobService.runJob("CheckIntegrity");
dialogContext.message.next("Running a vacuum on the database...");
await this.jobService.runJob("Vacuum");
dialogContext.message.next( dialogContext.message.next(
"Migrating content descriptors to new format..."); "Migrating content descriptors to new format...");
await this.jobService.runJob("MigrateContentDescriptors"); await this.jobService.runJob("MigrateContentDescriptors");
dialogContext.message.next("Calculating repository sizes..."); dialogContext.message.next("Calculating repository sizes...");
await this.jobService.runJob("CalculateSizes"); await this.jobService.runJob("CalculateSizes");
dialogContext.message.next("Checking integrity...");
await this.jobService.runJob("CheckIntegrity");
dialogContext.message.next("Finished repository startup"); dialogContext.message.next("Finished repository startup");
} }

@ -36,7 +36,7 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
private fileTags: { [key: number]: Tag[] } = {}; private fileTags: { [key: number]: Tag[] } = {};
constructor( constructor(
private errorBroker: LoggingService, private logger: LoggingService,
private tagService: TagService, private tagService: TagService,
) { ) {
} }
@ -148,16 +148,12 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
private async loadFileTags() { private async loadFileTags() {
await this.wrapAsyncOperation(async () => { await this.wrapAsyncOperation(async () => {
const promises = []; console.log("loading tags");
const loadFn = async (file: File) => { const mappings = await this.tagService.getFileTagMappings(this.files.map(f => f.cd));
this.fileTags[file.id] = await this.tagService.getTagsForFiles(
[file.cd]);
};
for (const file of this.files) { for (const file of this.files) {
promises.push(loadFn(file)); this.fileTags[file.id] = mappings[file.cd];
} }
await Promise.all(promises);
this.mapFileTagsToTagList(); this.mapFileTagsToTagList();
}); });
} }
@ -176,11 +172,11 @@ export class TagEditComponent implements AfterViewInit, OnChanges {
} }
private async wrapAsyncOperation<T>(cb: () => Promise<T>): Promise<T | undefined> { private async wrapAsyncOperation<T>(cb: () => Promise<T>): Promise<T | undefined> {
if (!this.busyIndicator) { if (!this.busyIndicator?.wrapAsyncOperation) {
try { try {
return cb(); return cb();
} catch (err: any) { } catch (err: any) {
this.errorBroker.error(err); this.logger.error(err);
return undefined; return undefined;
} }
} else { } else {

@ -29,16 +29,31 @@ export class TagService {
public async getTagsForFiles(cds: string[]): Promise<Tag[]> { public async getTagsForFiles(cds: string[]): Promise<Tag[]> {
let tags: Tag[] = []; let tags: Tag[] = [];
if (cds.length > 0) { if (cds.length > 0) {
tags = await MediarepoApi.getTagsForFiles({cds}).then(mapMany(mapNew(Tag))); tags = await MediarepoApi.getTagsForFiles({ cds }).then(mapMany(mapNew(Tag)));
} }
return tags; return tags;
} }
public async getFileTagMappings(cds: string[]): Promise<{ [key: string]: Tag[] }> {
if (cds.length > 0) {
return await MediarepoApi.getFileTagMap({ cds }).then((cdMappings) => {
let mappings: { [key: string]: Tag[] } = {};
console.log("TAG MAPPINGS", cdMappings);
for (const key in cdMappings) {
mappings[key] = cdMappings[key].map(mapNew(Tag));
}
return mappings;
});
} else {
return {};
}
}
public async createTags(tags: string[]): Promise<Tag[]> { public async createTags(tags: string[]): Promise<Tag[]> {
return MediarepoApi.createTags({tags}).then(mapMany(mapNew(Tag))); return MediarepoApi.createTags({ tags }).then(mapMany(mapNew(Tag)));
} }
public async changeFileTags(fileId: number, addedTags: number[], removedTags: number[]): Promise<Tag[]> { public async changeFileTags(fileId: number, addedTags: number[], removedTags: number[]): Promise<Tag[]> {
return MediarepoApi.changeFileTags({id: fileId, addedTags, removedTags}).then(mapMany(mapNew(Tag))); return MediarepoApi.changeFileTags({ id: fileId, addedTags, removedTags }).then(mapMany(mapNew(Tag)));
} }
} }

Loading…
Cancel
Save