Skip to content

Commit

Permalink
Add "Scenes duration" stat on statistics page
Browse files Browse the repository at this point in the history
  • Loading branch information
FleetingOrchard committed Aug 18, 2021
1 parent 3e78d64 commit c3d427c
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 14 deletions.
1 change: 1 addition & 0 deletions graphql/documents/queries/misc.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ query Stats {
stats {
scene_count,
scenes_size,
scenes_duration,
image_count,
images_size,
gallery_count,
Expand Down
1 change: 1 addition & 0 deletions graphql/schema/types/stats.graphql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type StatsResultType {
scene_count: Int!
scenes_size: Float!
scenes_duration: Float!
image_count: Int!
images_size: Float!
gallery_count: Int!
Expand Down
2 changes: 2 additions & 0 deletions pkg/api/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
tagsQB := repo.Tag()
scenesCount, _ := scenesQB.Count()
scenesSize, _ := scenesQB.Size()
scenesDuration, _ := scenesQB.Duration()
imageCount, _ := imageQB.Count()
imageSize, _ := imageQB.Size()
galleryCount, _ := galleryQB.Count()
Expand All @@ -149,6 +150,7 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err
ret = models.StatsResultType{
SceneCount: scenesCount,
ScenesSize: scenesSize,
ScenesDuration: scenesDuration,
ImageCount: imageCount,
ImagesSize: imageSize,
GalleryCount: galleryCount,
Expand Down
21 changes: 21 additions & 0 deletions pkg/models/mocks/SceneReaderWriter.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/models/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type SceneReader interface {
CountByMovieID(movieID int) (int, error)
Count() (int, error)
Size() (float64, error)
Duration() (float64, error)
// SizeCount() (string, error)
CountByStudioID(studioID int) (int, error)
CountByTagID(tagID int) (int, error)
Expand Down
4 changes: 4 additions & 0 deletions pkg/sqlite/scene.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ func (qb *sceneQueryBuilder) Size() (float64, error) {
return qb.runSumQuery("SELECT SUM(cast(size as double)) as sum FROM scenes", nil)
}

func (qb *sceneQueryBuilder) Duration() (float64, error) {
return qb.runSumQuery("SELECT SUM(cast(duration as double)) as sum FROM scenes", nil)
}

func (qb *sceneQueryBuilder) CountByStudioID(studioID int) (int, error) {
args := []interface{}{studioID}
return qb.runCountQuery(qb.buildCountQuery(scenesForStudioQuery), args)
Expand Down
39 changes: 25 additions & 14 deletions ui/v2.5/src/components/Stats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,34 +41,45 @@ export const Stats: React.FC = () => {
</div>
<div className="stats-element">
<p className="title">
<FormattedNumber
value={imagesSize.size}
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
imagesSize.unit
)}
/>
{` ${TextUtils.formatFileSizeUnit(imagesSize.unit)}`}
<FormattedNumber value={data.stats.movie_count} />
</p>
<p className="heading">
<FormattedMessage id="images-size" defaultMessage="Images size" />
<FormattedMessage id="movies" />
</p>
</div>
<div className="stats-element">
<p className="title">
<FormattedNumber value={data.stats.image_count} />
{` ${TextUtils.secondsAsTimeString(data.stats.scenes_duration, 3)}`}
</p>
<p className="heading">
<FormattedMessage id="images" />
<FormattedMessage
id="scenes-duration"
defaultMessage="Scenes duration"
/>
</p>
</div>
<div className="stats-element">
<p className="title">
<FormattedNumber value={data.stats.performer_count} />
</p>
<p className="heading">
<FormattedMessage id="performers" />
</p>
</div>
</div>
<div className="col col-sm-8 m-sm-auto row stats">
<div className="stats-element">
<p className="title">
<FormattedNumber value={data.stats.movie_count} />
<FormattedNumber
value={imagesSize.size}
maximumFractionDigits={TextUtils.fileSizeFractionalDigits(
imagesSize.unit
)}
/>
{` ${TextUtils.formatFileSizeUnit(imagesSize.unit)}`}
</p>
<p className="heading">
<FormattedMessage id="movies" />
<FormattedMessage id="images-size" defaultMessage="Images size" />
</p>
</div>
<div className="stats-element">
Expand All @@ -81,10 +92,10 @@ export const Stats: React.FC = () => {
</div>
<div className="stats-element">
<p className="title">
<FormattedNumber value={data.stats.performer_count} />
<FormattedNumber value={data.stats.image_count} />
</p>
<p className="heading">
<FormattedMessage id="performers" />
<FormattedMessage id="images" />
</p>
</div>
<div className="stats-element">
Expand Down
1 change: 1 addition & 0 deletions ui/v2.5/src/locales/en-GB.json
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@
"scene_count": "Scene Count",
"scene_id": "Scene ID",
"scenes": "Scenes",
"scenes-duration": "Scenes duration",
"scenes-size": "Scenes size",
"scenes_updated_at": "Scene Updated At",
"sceneTagger": "Scene Tagger",
Expand Down
107 changes: 107 additions & 0 deletions ui/v2.5/src/utils/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,112 @@ const fileSize = (bytes: number = 0) => {
};
};

class DurationUnit {
static readonly SECOND: DurationUnit = new DurationUnit(
"second",
"seconds",
"s",
1
);
static readonly MINUTE: DurationUnit = new DurationUnit(
"minute",
"minutes",
"m",
60
);
static readonly HOUR: DurationUnit = new DurationUnit(
"hour",
"hours",
"h",
DurationUnit.MINUTE.secs * 60
);
static readonly DAY: DurationUnit = new DurationUnit(
"day",
"days",
"D",
DurationUnit.HOUR.secs * 24
);
static readonly WEEK: DurationUnit = new DurationUnit(
"week",
"weeks",
"W",
DurationUnit.DAY.secs * 7
);
static readonly MONTH: DurationUnit = new DurationUnit(
"month",
"months",
"M",
DurationUnit.DAY.secs * 30
);
static readonly YEAR: DurationUnit = new DurationUnit(
"year",
"years",
"Y",
DurationUnit.DAY.secs * 365
);

static readonly DURATIONS: DurationUnit[] = [
DurationUnit.SECOND,
DurationUnit.MINUTE,
DurationUnit.HOUR,
DurationUnit.DAY,
DurationUnit.WEEK,
DurationUnit.MONTH,
DurationUnit.YEAR,
];

private constructor(
private readonly singular: string,
private readonly plural: string,
private readonly shortString: string,
public secs: number
) {}

toString() {
return this.shortString;
}
}

class DurationCount {
public constructor(
public readonly count: number,
public readonly duration: DurationUnit
) {}

toString() {
return this.count.toString() + this.duration.toString();
}
}

const secondsAsTime = (seconds: number = 0): DurationCount[] => {
if (Number.isNaN(parseFloat(String(seconds))) || !Number.isFinite(seconds))
return [new DurationCount(0, DurationUnit.DURATIONS[0])];

const result = [];
let remainingSeconds = seconds;
// Run down the possible durations and pull them out
for (let i = DurationUnit.DURATIONS.length - 1; i >= 0; i--) {
const q = Math.floor(remainingSeconds / DurationUnit.DURATIONS[i].secs);
if (q !== 0) {
remainingSeconds %= DurationUnit.DURATIONS[i].secs;
result.push(new DurationCount(q, DurationUnit.DURATIONS[i]));
}
}
return result;
};

const timeAsString = (time: DurationCount[]): string => {
return time.join(" ");
};

const secondsAsTimeString = (
seconds: number = 0,
maxUnitCount: number = 2
): string => {
const timeArray = secondsAsTime(seconds).slice(0, maxUnitCount);
return timeAsString(timeArray);
};

const formatFileSizeUnit = (u: Unit) => {
const i = Units.indexOf(u);
return shortUnits[i];
Expand Down Expand Up @@ -206,6 +312,7 @@ const TextUtils = {
instagramURL,
formatDate,
capitalize,
secondsAsTimeString,
};

export default TextUtils;

0 comments on commit c3d427c

Please sign in to comment.