Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance: Prevent loading already loaded images on first photo-open #3435

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
81 changes: 0 additions & 81 deletions frontend/src/common/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ import PhotoSwipe from "photoswipe";
import PhotoSwipeUI_Default from "photoswipe/dist/photoswipe-ui-default.js";
import Event from "pubsub-js";
import Util from "util.js";
import Api from "./api";
import Thumb from "model/thumb";

const thumbs = window.__CONFIG__.thumbs;

Expand Down Expand Up @@ -219,85 +217,6 @@ class Viewer {

return "fit_7680";
}

static show(ctx, index) {
if (ctx.loading || !ctx.listen || ctx.viewer.loading || !ctx.results[index]) {
return false;
}

const selected = ctx.results[index];

if (!ctx.viewer.dirty && ctx.viewer.results && ctx.viewer.results.length > index) {
// Reuse existing viewer result if possible.
let i = -1;

if (ctx.viewer.results[index] && ctx.viewer.results[index].UID === selected.UID) {
i = index;
} else {
i = ctx.viewer.results.findIndex((p) => p.UID === selected.UID);
}

if (
i > -1 &&
(((ctx.viewer.complete || ctx.complete) &&
ctx.viewer.results.length >= ctx.results.length) ||
i + ctx.viewer.batchSize <= ctx.viewer.results.length)
) {
ctx.$viewer.show(ctx.viewer.results, i);
return;
}
}

// Fetch photos from server API.
ctx.viewer.loading = true;

const params = ctx.searchParams();
params.count = params.offset + ctx.viewer.batchSize;
params.offset = 0;

// Fetch viewer results from API.
return Api.get("photos/view", { params })
.then((response) => {
const count = response && response.data ? response.data.length : 0;
if (count === 0) {
ctx.$notify.warn(ctx.$gettext("No pictures found"));
ctx.viewer.dirty = true;
ctx.viewer.complete = false;
return;
}

// Process response.
if (response.headers && response.headers["x-count"]) {
const c = parseInt(response.headers["x-count"]);
const l = parseInt(response.headers["x-limit"]);
ctx.viewer.complete = c < l;
} else {
ctx.viewer.complete = ctx.complete;
}

let i;

if (response.data[index] && response.data[index].UID === selected.UID) {
i = index;
} else {
i = response.data.findIndex((p) => p.UID === selected.UID);
}

ctx.viewer.results = Thumb.wrap(response.data);

// Show photos.
ctx.$viewer.show(ctx.viewer.results, i);
ctx.viewer.dirty = false;
})
.catch(() => {
ctx.viewer.dirty = true;
ctx.viewer.complete = false;
})
.finally(() => {
// Unblock.
ctx.viewer.loading = false;
});
}
}

export default Viewer;
103 changes: 85 additions & 18 deletions frontend/src/model/photo.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const TimeZoneUTC = "UTC";
const num = "numeric";
const short = "short";
const long = "long";
const thumbs = window.__CONFIG__.thumbs;

export const DATE_FULL = {
year: num,
Expand All @@ -94,6 +95,51 @@ export let BatchSize = 120;
export class Photo extends RestModel {
constructor(values) {
super(values);

let mainFile;
if (this.Files) {
mainFile = this.mainFile();
}
const mainFileHash = this.generateMainFileHash(mainFile, this.Hash);
this.Thumbs = this.generateThumbs(mainFile, mainFileHash);
this.DownloadUrl = this.getDownloadUrl(mainFileHash);
}

generateThumbs(mainFile, mainFileHash) {
let sourceWidth = this.Width;
let sourceHeight = this.Height;
if (mainFile) {
sourceWidth = mainFile.Width;
sourceHeight = mainFile.Height;
}

if (!this.Hash) {
return {};
}

const result = {};
for (let i = 0; i < thumbs.length; i++) {
let t = thumbs[i];
const size = this.calculateSizeFromProps(t.w, t.h, sourceWidth, sourceHeight);

// we know the thumbnail url can't be cached because the size-parameter changes on every call
const url = this.generateThumbnailUrlUnmemoized(
mainFileHash,
this.videoFile(),
config.staticUri,
config.contentUri,
config.previewToken,
t.size
);

result[t.size] = {
src: url,
w: size.width,
h: size.height,
};
}

return result;
}

getDefaults() {
Expand Down Expand Up @@ -189,6 +235,8 @@ export class Photo extends RestModel {
EditedAt: null,
CheckedAt: null,
DeletedAt: null,
Thumbs: {},
DownloadUrl: "",
};
}

Expand Down Expand Up @@ -587,24 +635,39 @@ export class Photo extends RestModel {
);
}

generateThumbnailUrl = memoizeOne(
(mainFileHash, videoFile, staticUri, contentUri, previewToken, size) => {
let hash = mainFileHash;

if (!hash) {
if (videoFile && videoFile.Hash) {
return `${contentUri}/t/${videoFile.Hash}/${previewToken}/${size}`;
}

return `${staticUri}/img/404.jpg`;
/**
* use this one if you know the parameters changed AND yout task is so
* performance critical, that you benefit from skipping the parameter-changed
* -check from memoizeOne.
*
* If you don't know wether the parameters changed or if your task is not
* incredibly performance critical, use the memoized 'generateThumbnailUrl'
*/
generateThumbnailUrlUnmemoized = (
mainFileHash,
videoFile,
staticUri,
contentUri,
previewToken,
size
) => {
let hash = mainFileHash;

if (!hash) {
if (videoFile && videoFile.Hash) {
return `${contentUri}/t/${videoFile.Hash}/${previewToken}/${size}`;
}

return `${contentUri}/t/${hash}/${previewToken}/${size}`;
return `${staticUri}/img/404.jpg`;
}
);

getDownloadUrl() {
return `${config.apiUri}/dl/${this.mainFileHash()}?t=${config.downloadToken}`;
return `${contentUri}/t/${hash}/${previewToken}/${size}`;
};

generateThumbnailUrl = memoizeOne(this.generateThumbnailUrlUnmemoized);

getDownloadUrl(mainFileHash = this.mainFileHash()) {
return `${config.apiUri}/dl/${mainFileHash}?t=${config.downloadToken}`;
}

downloadAll() {
Expand Down Expand Up @@ -665,13 +728,13 @@ export class Photo extends RestModel {
});
}

calculateSize(width, height) {
if (width >= this.Width && height >= this.Height) {
calculateSizeFromProps(width, height, sourceWidth, sourceHeight) {
if (width >= sourceWidth && height >= sourceHeight) {
// Smaller
return { width: this.Width, height: this.Height };
return { width: sourceWidth, height: sourceHeight };
}

const srcAspectRatio = this.Width / this.Height;
const srcAspectRatio = sourceWidth / sourceHeight;
const maxAspectRatio = width / height;

let newW, newH;
Expand All @@ -687,6 +750,10 @@ export class Photo extends RestModel {
return { width: newW, height: newH };
}

calculateSize(width, height) {
return this.calculateSizeFromProps(width, height, this.Width, this.Height);
}

getDateString(showTimeZone) {
return this.generateDateString(
showTimeZone,
Expand Down
47 changes: 0 additions & 47 deletions frontend/src/model/thumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,53 +95,6 @@ export class Thumb extends Model {
return result;
}

static fromPhotos(photos) {
let result = [];
const n = photos.length;

for (let i = 0; i < n; i++) {
result.push(this.fromPhoto(photos[i]));
}

return result;
}

static fromPhoto(photo) {
if (photo.Files) {
return this.fromFile(photo, photo.mainFile());
}

if (!photo || !photo.Hash) {
return this.notFound();
}

const result = {
UID: photo.UID,
Title: photo.Title,
TakenAtLocal: photo.getDateString(),
Description: photo.Description,
Favorite: photo.Favorite,
Playable: photo.isPlayable(),
DownloadUrl: this.downloadUrl(photo),
Width: photo.Width,
Height: photo.Height,
Thumbs: {},
};

for (let i = 0; i < thumbs.length; i++) {
let t = thumbs[i];
let size = photo.calculateSize(t.w, t.h);

result.Thumbs[t.size] = {
src: photo.thumbnailUrl(t.size),
w: size.width,
h: size.height,
};
}

return new this(result);
}

static fromFile(photo, file) {
if (!photo || !file || !file.Hash) {
return this.notFound();
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/page/album/photos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ import {Photo, MediaLive, MediaRaw, MediaVideo, MediaAnimated} from "model/photo
import Album from "model/album";
import Thumb from "model/thumb";
import Event from "pubsub-js";
import Viewer from "common/viewer";

export default {
name: 'PPageAlbumPhotos',
Expand Down Expand Up @@ -243,12 +242,12 @@ export default {
if (selected.isPlayable()) {
this.$viewer.play({video: selected, album: this.album});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
this.$viewer.show(this.results, index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0);
} else {
Viewer.show(this, index);
this.$viewer.show(this.results, index);
}

return true;
Expand Down
5 changes: 2 additions & 3 deletions frontend/src/page/photos.vue
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@
<script>
import {MediaAnimated, MediaLive, MediaRaw, MediaVideo, Photo} from "model/photo";
import Thumb from "model/thumb";
import Viewer from "common/viewer";
import Event from "pubsub-js";

export default {
Expand Down Expand Up @@ -325,12 +324,12 @@ export default {
if (selected.isPlayable()) {
this.$viewer.play({video: selected});
} else {
this.$viewer.show(Thumb.fromPhotos(this.results), index);
this.$viewer.show(this.results, index);
}
} else if (showMerged) {
this.$viewer.show(Thumb.fromFiles([selected]), 0);
} else {
Viewer.show(this, index);
this.$viewer.show(this.results, index);
}

return true;
Expand Down
8 changes: 4 additions & 4 deletions frontend/tests/unit/model/thumb_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ describe("model/thumb", () => {
],
};
const photo = new Photo(values);
const result = Thumb.fromPhoto(photo);
const result = photo;
assert.equal(result.UID, "ABC123");
assert.equal(result.Description, "Nice description 3");
assert.equal(result.Width, 500);
Expand All @@ -208,7 +208,7 @@ describe("model/thumb", () => {
Description: "Nice description 3",
};
const photo3 = new Photo(values3);
const result2 = Thumb.fromPhoto(photo3);
const result2 = photo3;
assert.equal(result2.UID, "");
const values2 = {
ID: 8,
Expand All @@ -221,7 +221,7 @@ describe("model/thumb", () => {
Hash: "xdf45m",
};
const photo2 = new Photo(values2);
const result3 = Thumb.fromPhoto(photo2);
const result3 = photo2;
assert.equal(result3.UID, "ABC123");
assert.equal(result3.Title, "Crazy Cat");
assert.equal(result3.Description, "Nice description");
Expand All @@ -247,7 +247,7 @@ describe("model/thumb", () => {
};
const photo = new Photo(values);
const Photos = [photo];
const result = Thumb.fromPhotos(Photos);
const result = Photos;
assert.equal(result[0].UID, "ABC123");
assert.equal(result[0].Description, "Nice description 3");
assert.equal(result[0].Width, 500);
Expand Down