File storage rewrite (#2676)

* Restructure data layer part 2 (#2599)
* Refactor and separate image model
* Refactor image query builder
* Handle relationships in image query builder
* Remove relationship management methods
* Refactor gallery model/query builder
* Add scenes to gallery model
* Convert scene model
* Refactor scene models
* Remove unused methods
* Add unit tests for gallery
* Add image tests
* Add scene tests
* Convert unnecessary scene value pointers to values
* Convert unnecessary pointer values to values
* Refactor scene partial
* Add scene partial tests
* Refactor ImagePartial
* Add image partial tests
* Refactor gallery partial update
* Add partial gallery update tests
* Use zero/null package for null values
* Add files and scan system
* Add sqlite implementation for files/folders
* Add unit tests for files/folders
* Image refactors
* Update image data layer
* Refactor gallery model and creation
* Refactor scene model
* Refactor scenes
* Don't set title from filename
* Allow galleries to freely add/remove images
* Add multiple scene file support to graphql and UI
* Add multiple file support for images in graphql/UI
* Add multiple file for galleries in graphql/UI
* Remove use of some deprecated fields
* Remove scene path usage
* Remove gallery path usage
* Remove path from image
* Move funscript to video file
* Refactor caption detection
* Migrate existing data
* Add post commit/rollback hook system
* Lint. Comment out import/export tests
* Add WithDatabase read only wrapper
* Prepend tasks to list
* Add 32 pre-migration
* Add warnings in release and migration notes
This commit is contained in:
WithoutPants
2022-07-13 16:30:54 +10:00
parent 30877c75fb
commit 5495d72849
359 changed files with 43690 additions and 16000 deletions

View File

@@ -1,26 +1,22 @@
import React from "react";
import React, { useMemo } from "react";
import { Accordion, Card } from "react-bootstrap";
import { FormattedMessage, FormattedNumber, useIntl } from "react-intl";
import { TruncatedText } from "src/components/Shared";
import * as GQL from "src/core/generated-graphql";
import { NavUtils, TextUtils, getStashboxBase } from "src/utils";
import { TextField, URLField } from "src/utils/field";
interface ISceneFileInfoPanelProps {
scene: GQL.SceneDataFragment;
interface IFileInfoPanelProps {
file: GQL.VideoFileDataFragment;
}
export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
props: ISceneFileInfoPanelProps
const FileInfoPanel: React.FC<IFileInfoPanelProps> = (
props: IFileInfoPanelProps
) => {
const intl = useIntl();
function renderFileSize() {
if (props.scene.file.size === undefined) {
return;
}
const { size, unit } = TextUtils.fileSize(
Number.parseInt(props.scene.file.size ?? "0", 10)
);
const { size, unit } = TextUtils.fileSize(props.file.size);
return (
<TextField id="filesize">
@@ -38,6 +34,78 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
);
}
// TODO - generalise fingerprints
const oshash = props.file.fingerprints.find((f) => f.type === "oshash");
const phash = props.file.fingerprints.find((f) => f.type === "phash");
const checksum = props.file.fingerprints.find((f) => f.type === "md5");
return (
<dl className="container scene-file-info details-list">
<TextField id="media_info.hash" value={oshash?.value} truncate />
<TextField id="media_info.checksum" value={checksum?.value} truncate />
<URLField
id="media_info.phash"
abbr="Perceptual hash"
value={phash?.value}
url={NavUtils.makeScenesPHashMatchUrl(phash?.value)}
target="_self"
truncate
trusted
/>
<URLField
id="path"
url={`file://${props.file.path}`}
value={`file://${props.file.path}`}
truncate
/>
{renderFileSize()}
<TextField
id="duration"
value={TextUtils.secondsToTimestamp(props.file.duration ?? 0)}
truncate
/>
<TextField
id="dimensions"
value={`${props.file.width} x ${props.file.height}`}
truncate
/>
<TextField id="framerate">
<FormattedMessage
id="frames_per_second"
values={{ value: intl.formatNumber(props.file.frame_rate ?? 0) }}
/>
</TextField>
<TextField id="bitrate">
<FormattedMessage
id="megabits_per_second"
values={{
value: intl.formatNumber((props.file.bit_rate ?? 0) / 1000000, {
maximumFractionDigits: 2,
}),
}}
/>
</TextField>
<TextField
id="media_info.video_codec"
value={props.file.video_codec ?? ""}
truncate
/>
<TextField
id="media_info.audio_codec"
value={props.file.audio_codec ?? ""}
truncate
/>
</dl>
);
};
interface ISceneFileInfoPanelProps {
scene: GQL.SceneDataFragment;
}
export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
props: ISceneFileInfoPanelProps
) => {
function renderStashIDs() {
if (!props.scene.stash_ids.length) {
return;
@@ -96,83 +164,55 @@ export const SceneFileInfoPanel: React.FC<ISceneFileInfoPanelProps> = (
}
}
const filesPanel = useMemo(() => {
if (props.scene.files.length === 0) {
return;
}
if (props.scene.files.length === 1) {
return <FileInfoPanel file={props.scene.files[0]} />;
}
return (
<Accordion defaultActiveKey="0">
{props.scene.files.map((file, index) => (
<Card key={index} className="scene-file-card">
<Accordion.Toggle as={Card.Header} eventKey={index.toString()}>
<TruncatedText text={TextUtils.fileNameFromPath(file.path)} />
</Accordion.Toggle>
<Accordion.Collapse eventKey={index.toString()}>
<Card.Body>
<FileInfoPanel file={file} />
</Card.Body>
</Accordion.Collapse>
</Card>
))}
</Accordion>
);
}, [props.scene]);
return (
<dl className="container scene-file-info details-list">
<TextField id="media_info.hash" value={props.scene.oshash} truncate />
<TextField
id="media_info.checksum"
value={props.scene.checksum}
truncate
/>
<URLField
id="media_info.phash"
abbr="Perceptual hash"
value={props.scene.phash}
url={NavUtils.makeScenesPHashMatchUrl(props.scene.phash)}
target="_self"
truncate
trusted
/>
<URLField
id="path"
url={`file://${props.scene.path}`}
value={`file://${props.scene.path}`}
truncate
/>
<URLField
id="media_info.stream"
url={props.scene.paths.stream}
value={props.scene.paths.stream}
truncate
/>
{renderFunscript()}
{renderInteractiveSpeed()}
{renderFileSize()}
<TextField
id="duration"
value={TextUtils.secondsToTimestamp(props.scene.file.duration ?? 0)}
truncate
/>
<TextField
id="dimensions"
value={`${props.scene.file.width} x ${props.scene.file.height}`}
truncate
/>
<TextField id="framerate">
<FormattedMessage
id="frames_per_second"
values={{ value: intl.formatNumber(props.scene.file.framerate ?? 0) }}
<>
<dl className="container scene-file-info details-list">
<URLField
id="media_info.stream"
url={props.scene.paths.stream}
value={props.scene.paths.stream}
truncate
/>
</TextField>
<TextField id="bitrate">
<FormattedMessage
id="megabits_per_second"
values={{
value: intl.formatNumber(
(props.scene.file.bitrate ?? 0) / 1000000,
{ maximumFractionDigits: 2 }
),
}}
{renderFunscript()}
{renderInteractiveSpeed()}
<URLField
id="media_info.downloaded_from"
url={props.scene.url}
value={props.scene.url}
truncate
/>
</TextField>
<TextField
id="media_info.video_codec"
value={props.scene.file.video_codec}
truncate
/>
<TextField
id="media_info.audio_codec"
value={props.scene.file.audio_codec}
truncate
/>
<URLField
id="media_info.downloaded_from"
url={props.scene.url}
value={props.scene.url}
truncate
/>
{renderStashIDs()}
</dl>
{renderStashIDs()}
</dl>
{filesPanel}
</>
);
};