1
0
mirror of https://github.com/bpatrik/pigallery2.git synced 2025-12-09 23:32:14 +02:00

Add directory filtering for listing #1015

This commit is contained in:
Patrik J. Braun
2025-09-07 16:04:03 +02:00
parent 849c9aa07b
commit c85ce3477e
6 changed files with 53 additions and 22 deletions

View File

@@ -6,6 +6,7 @@ export class SessionContext {
user: ContextUser; user: ContextUser;
// New structured projection with prebuilt SQL and params // New structured projection with prebuilt SQL and params
projectionQuery?: Brackets; projectionQuery?: Brackets;
projectionQueryForSubDir?: Brackets; // only the directory part of the query, where it filters 'directories' instead of 'directory' aliases
hasDirectoryProjection: boolean; hasDirectoryProjection: boolean;
} }

View File

@@ -15,7 +15,6 @@ import {DuplicatesDTO} from '../../../common/entities/DuplicatesDTO';
import {ReIndexingSensitivity} from '../../../common/config/private/PrivateConfig'; import {ReIndexingSensitivity} from '../../../common/config/private/PrivateConfig';
import {DiskManager} from '../fileaccess/DiskManager'; import {DiskManager} from '../fileaccess/DiskManager';
import {SessionContext} from '../SessionContext'; import {SessionContext} from '../SessionContext';
import {ProjectedDirectoryCacheEntity} from './enitites/ProjectedDirectoryCacheEntity';
const LOG_TAG = '[GalleryManager]'; const LOG_TAG = '[GalleryManager]';
@@ -339,16 +338,6 @@ export class GalleryManager {
partialDirId: number partialDirId: number
): Promise<ParentDirectoryDTO> { ): Promise<ParentDirectoryDTO> {
const select = [
'directory',
'directories',
'cover.name',
'coverDirectory.name',
'coverDirectory.path',
'dcover.name',
'dcoverDirectory.name',
'dcoverDirectory.path',
];
const query = connection const query = connection
.getRepository(DirectoryEntity) .getRepository(DirectoryEntity)
.createQueryBuilder('directory') .createQueryBuilder('directory')
@@ -375,6 +364,12 @@ export class GalleryManager {
'dcoverDirectory.path', 'dcoverDirectory.path',
]); ]);
// search does not return a directory if that is recursively having 0 media
// gallery listing should otherwise, we won't be able to trigger lazy indexing
// this behavior lets us explicitly hid a directory if it is explicitly blocked
if(session.projectionQueryForSubDir) {
query.andWhere(session.projectionQueryForSubDir);
}
if (!session.projectionQuery) { if (!session.projectionQuery) {
query.leftJoinAndSelect('directory.media', 'media'); query.leftJoinAndSelect('directory.media', 'media');
} }

View File

@@ -478,11 +478,13 @@ export class SearchManager {
} }
public async prepareAndBuildWhereQuery( public async prepareAndBuildWhereQuery(
queryIN: SearchQueryDTO, queryIN: SearchQueryDTO, directoryOnly = false,
directoryOnly = false aliases: { [key: string]: string } = {}): Promise<Brackets> {
): Promise<Brackets> { let query = await this.prepareQuery(queryIN);
const query = await this.prepareQuery(queryIN); if (directoryOnly) {
return this.buildWhereQuery(query, directoryOnly); query = this.filterDirectoryQuery(query);
}
return this.buildWhereQuery(query, directoryOnly, aliases);
} }
public async prepareQuery(queryIN: SearchQueryDTO): Promise<SearchQueryDTO> { public async prepareQuery(queryIN: SearchQueryDTO): Promise<SearchQueryDTO> {
@@ -496,11 +498,13 @@ export class SearchManager {
* Builds the SQL Where query from search query * Builds the SQL Where query from search query
* @param query input search query * @param query input search query
* @param directoryOnly Only builds directory related queries * @param directoryOnly Only builds directory related queries
* @param aliases for SQL alias mapping
* @private * @private
*/ */
public buildWhereQuery( public buildWhereQuery(
query: SearchQueryDTO, query: SearchQueryDTO,
directoryOnly = false directoryOnly = false,
aliases: { [key: string]: string } = {}
): Brackets { ): Brackets {
const queryId = (query as SearchQueryDTOWithID).queryId; const queryId = (query as SearchQueryDTOWithID).queryId;
switch (query.type) { switch (query.type) {
@@ -1039,10 +1043,10 @@ export class SearchManager {
new RegExp('\\\\', 'g'), new RegExp('\\\\', 'g'),
'/' '/'
); );
const alias = aliases['directory'] ?? 'directory';
textParam['fullPath' + queryId] = createMatchString(dirPathStr); textParam['fullPath' + queryId] = createMatchString(dirPathStr);
q[whereFN]( q[whereFN](
`directory.path ${LIKE} :fullPath${queryId} COLLATE ` + SQL_COLLATE, `${alias}.path ${LIKE} :fullPath${queryId} COLLATE ` + SQL_COLLATE,
textParam textParam
); );
@@ -1053,7 +1057,7 @@ export class SearchManager {
directoryPath.name directoryPath.name
); );
dq[whereFNRev]( dq[whereFNRev](
`directory.name ${LIKE} :dirName${queryId} COLLATE ${SQL_COLLATE}`, `${alias}.name ${LIKE} :dirName${queryId} COLLATE ${SQL_COLLATE}`,
textParam textParam
); );
if (dirPathStr.includes('/')) { if (dirPathStr.includes('/')) {
@@ -1061,7 +1065,7 @@ export class SearchManager {
directoryPath.parent directoryPath.parent
); );
dq[whereFNRev]( dq[whereFNRev](
`directory.path ${LIKE} :parentName${queryId} COLLATE ${SQL_COLLATE}`, `${alias}.path ${LIKE} :parentName${queryId} COLLATE ${SQL_COLLATE}`,
textParam textParam
); );
} }

View File

@@ -6,6 +6,7 @@ import {ANDSearchQuery, SearchQueryDTO, SearchQueryTypes} from '../../../common/
import {SharingEntity} from './enitites/SharingEntity'; import {SharingEntity} from './enitites/SharingEntity';
import {ObjectManagers} from '../ObjectManagers'; import {ObjectManagers} from '../ObjectManagers';
import {Logger} from '../../Logger'; import {Logger} from '../../Logger';
import {Utils} from '../../../common/Utils';
const LOG_TAG = '[SessionManager]'; const LOG_TAG = '[SessionManager]';
@@ -68,6 +69,9 @@ export class SessionManager {
// Build the Brackets-based query // Build the Brackets-based query
context.projectionQuery = await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(finalQuery); context.projectionQuery = await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(finalQuery);
context.hasDirectoryProjection = ObjectManagers.getInstance().SearchManager.hasDirectoryQuery(finalQuery); context.hasDirectoryProjection = ObjectManagers.getInstance().SearchManager.hasDirectoryQuery(finalQuery);
if (context.hasDirectoryProjection) {
context.projectionQueryForSubDir = await ObjectManagers.getInstance().SearchManager.prepareAndBuildWhereQuery(finalQuery, true, {directory: 'directories'});
}
context.user.projectionKey = this.createProjectionKey(finalQuery); context.user.projectionKey = this.createProjectionKey(finalQuery);
if (SearchQueryUtils.isQueryEmpty(finalQuery)) { if (SearchQueryUtils.isQueryEmpty(finalQuery)) {
Logger.silly(LOG_TAG, 'Empty Projection query.'); Logger.silly(LOG_TAG, 'Empty Projection query.');

View File

@@ -12,6 +12,7 @@ import {Utils} from './Utils';
export const SearchQueryUtils = { export const SearchQueryUtils = {
negate: (query: SearchQueryDTO): SearchQueryDTO => { negate: (query: SearchQueryDTO): SearchQueryDTO => {
query = Utils.clone(query)
switch (query.type) { switch (query.type) {
case SearchQueryTypes.AND: case SearchQueryTypes.AND:
query.type = SearchQueryTypes.OR; query.type = SearchQueryTypes.OR;

View File

@@ -64,7 +64,7 @@ describe('GalleryManager', (sqlHelper: DBTestHelper) => {
it('should return all media when no projection query is provided', async () => { it('should return all media when no projection query is provided', async () => {
// Setup a session with no projection query // Setup a session with no projection query
const session = DBTestHelper.defaultSession const session = DBTestHelper.defaultSession;
// Get the directory contents without any filtering using our directory's name and path // Get the directory contents without any filtering using our directory's name and path
@@ -142,7 +142,33 @@ describe('GalleryManager', (sqlHelper: DBTestHelper) => {
}); });
it('blocks child directory when blockQuery contains normal directory search', async () => {
const root = sqlHelper.testGalleyEntities.dir;
const child = sqlHelper.testGalleyEntities.subDir;
const blockQuery = {
type: SearchQueryTypes.directory,
matchType: TextSearchQueryMatchTypes.like,
text: child.name
} as any;
const session = await ObjectManagers.getInstance().SessionManager.buildContext({
blockQuery,
overrideAllowBlockList: true
} as any);
const dirInfo = await galleryManager.getDirIdAndTime(connection, root.name, root.path);
const directory = await galleryManager.getParentDirFromId(connection, session, dirInfo.id);
expect(((await galleryManager.getParentDirFromId(connection, DBTestHelper.defaultSession, dirInfo.id)).directories || [])
.map(d => d.name)).to.include(child.name);
expect(((await galleryManager.getParentDirFromId(connection, session, dirInfo.id)).directories || [])
.map(d => d.name)).to.not.include(child.name);
});
}); });
describe('GalleryManager.listDirectory - reindexing severities and projection behavior', () => { describe('GalleryManager.listDirectory - reindexing severities and projection behavior', () => {
const origStatSync = fs.statSync; const origStatSync = fs.statSync;