Skip to content

Commit

Permalink
Merge pull request #12 from rurku/dynamic_tree_load
Browse files Browse the repository at this point in the history
Dynamic tree load
  • Loading branch information
rurku authored Oct 21, 2018
2 parents b21cecf + 022ffe9 commit 7152013
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 31 deletions.
2 changes: 2 additions & 0 deletions ShowPics.Data.Abstractions/IFilesData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public interface IFilesData
{
Task<ICollection<Folder>> GetAllAsync(CancellationToken cancellationToken = default(CancellationToken));
ICollection<Folder> GetAll();
ICollection<Folder> GetTopLevelFolders(int foldersDepth, int filesDepth);
Folder GetFolder(string logicalPath, int foldersDepth, int filesDepth);
void Remove(File file);
void Remove(Folder folder);
void Add(File file);
Expand Down
69 changes: 69 additions & 0 deletions ShowPics.Data/FilesData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using System.Linq;
using Microsoft.EntityFrameworkCore.Query;

namespace ShowPics.Data
{
public class FilesData : IFilesData
Expand Down Expand Up @@ -42,6 +45,72 @@ public ICollection<Folder> GetAll()
return _dbContext.Folders.Include(x => x.Files).ToListAsync().Result;
}

public ICollection<Folder> GetTopLevelFolders(int foldersDepth, int filesDepth)
{
if (foldersDepth < 0 || foldersDepth > 2)
throw new ArgumentOutOfRangeException(nameof(foldersDepth), "Argument must be between 0 and 2");
if (filesDepth < 0 || filesDepth > 2)
throw new ArgumentOutOfRangeException(nameof(filesDepth), "Argument must be between 0 and 2");
if (filesDepth > foldersDepth + 1)
throw new ArgumentOutOfRangeException(nameof(filesDepth), $"Argument must not be greater than {nameof(foldersDepth)} + 1");

IQueryable<Folder> foldersQueryable = _dbContext.Folders;
if (foldersDepth > 0)
{
var includable = foldersQueryable.Include(x => x.Children);
if (foldersDepth > 1)
{
includable = includable.ThenInclude(x => x.Children);
}
foldersQueryable = includable;
}
if (filesDepth > 0)
{
foldersQueryable = foldersQueryable.Include(x => x.Files);

if (filesDepth > 1)
{
foldersQueryable = foldersQueryable.Include(x => x.Children).ThenInclude(x => x.Files);
}
}

var result = foldersQueryable.Where(x => x.ParentId == null).ToListAsync().Result;
return result;
}

public Folder GetFolder(string logicalPath, int foldersDepth, int filesDepth)
{
if (foldersDepth < 0 || foldersDepth > 2)
throw new ArgumentOutOfRangeException(nameof(foldersDepth), "Argument must be between 0 and 2");
if (filesDepth < 0 || filesDepth > 2)
throw new ArgumentOutOfRangeException(nameof(filesDepth), "Argument must be between 0 and 2");
if (filesDepth > foldersDepth + 1)
throw new ArgumentOutOfRangeException(nameof(filesDepth), $"Argument must not be greater than {nameof(foldersDepth)} + 1");

IQueryable<Folder> foldersQueryable = _dbContext.Folders;
if (foldersDepth > 0)
{
var includable = foldersQueryable.Include(x => x.Children);
if (foldersDepth > 1)
{
includable = includable.ThenInclude(x => x.Children);
}
foldersQueryable = includable;
}
if (filesDepth > 0)
{
foldersQueryable = foldersQueryable.Include(x => x.Files);

if (filesDepth > 1)
{
foldersQueryable = foldersQueryable.Include(x => x.Children).ThenInclude(x => x.Files);
}
}

var result = foldersQueryable.SingleOrDefaultAsync(x => x.Path == logicalPath).Result;
return result;
}

public async Task<ICollection<Folder>> GetAllAsync(CancellationToken cancellationToken = default(CancellationToken))
{
return await _dbContext.Folders.Include(x => x.Files).ToListAsync(cancellationToken);
Expand Down
3 changes: 2 additions & 1 deletion ShowPics.Dtos/DirectoryDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace ShowPics.Dtos
public class DirectoryDto : FileSystemObject
{
[JsonProperty(Order = 0)]
public IList<FileSystemObject> Children { get; set; } = new List<FileSystemObject>();
public IList<FileSystemObject> Children { get; set; }
public bool HasSubdirectories { get; set; }
}
}
1 change: 1 addition & 0 deletions ShowPics.Dtos/FileSystemObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public abstract class FileSystemObject
{
public string Type => GetType().Name;
public virtual string Path { get; set; }
public virtual string ApiPath { get; set; }
public virtual string Name { get; set; }
}
}
10 changes: 10 additions & 0 deletions ShowPics.Utilities/PathHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ public string GetThumbnailPath(string originalPath, bool isFile)
throw new Exception($"Could not map original path '{originalPath}' to thumbnail path.");
}

public string GetApiPath(string originalPath)
{
if ((originalPath + "/").StartsWith(_folderSettings.Value.OriginalsLogicalPrefix + "/"))
{
var path = JoinLogicalPaths("api/Files", originalPath.Substring(_folderSettings.Value.OriginalsLogicalPrefix.Length).TrimStart('/'));
return path;
}
throw new Exception($"Could not map original path '{originalPath}' to thumbnail path.");
}

public string GetParentPath(string logicalPath)
{
var split = logicalPath.Split('/', options: StringSplitOptions.RemoveEmptyEntries);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
export class FileSystemObject {
type: string;
path: string;
apiPath: string;
thumbnailPath: string;
name: string;
children: FileSystemObject[];
contentType: string;
width: number;
height: number;
subfolders: FileSystemObject[];
hasSubdirectories: boolean;
subdirectories: FileSystemObject[];
}

export class FileSystemObjectTypes {
Expand Down
16 changes: 8 additions & 8 deletions ShowPics/ClientApp/src/app/media-browser/file.service.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';
import { FileSystemObject } from './file-service-dtos';

@Injectable()
export class FileService {

private filesUrl = 'api/Files'; // URL to web api
public static readonly rootUrl = '/api/Files'; // URL to web api

private static handleError(error: any): Promise<any> {
console.error('An error occurred', error); // for demo purposes only
Expand All @@ -17,14 +17,14 @@ export class FileService {

constructor(private http: HttpClient) { }

getFiles(): Observable<FileSystemObject> {
return this.http.get(this.filesUrl)
getFiles(path: string, depth: number): Observable<FileSystemObject> {
return this.http.get(path, {
params: {
depth: depth.toString()
}
})
.pipe(
catchError(FileService.handleError)
);
}

getUri(path: string) {
return "/" + this.filesUrl + "/" + path;
}
}
36 changes: 24 additions & 12 deletions ShowPics/ClientApp/src/app/media-browser/tree/tree.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FileService } from '../file.service';
import { FileSystemObject, FileSystemObjectTypes } from '../file-service-dtos';
import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions } from 'angular-tree-component';
import { TREE_ACTIONS, KEYS, IActionMapping, ITreeOptions, TreeNode } from 'angular-tree-component';
import { pipe, Observable, of } from 'rxjs';
import { map, tap } from 'rxjs/operators';

@Component({
selector: 'app-tree',
Expand All @@ -14,32 +16,42 @@ export class TreeComponent implements OnInit {

options: ITreeOptions = {
idField: 'path',
childrenField: 'subfolders'
getChildren: (treeNode: TreeNode) => this.getChildren(treeNode.data).toPromise(),
hasChildrenField: 'hasSubdirectories',
childrenField: 'subdirectories'
}


@Output() onSelected = new EventEmitter<FileSystemObject>();

constructor(private fileService: FileService) { }

getChildren(node: FileSystemObject): Observable<FileSystemObject[]> {
if (node.children == null) {
return this.fileService.getFiles(node.apiPath, 1)
.pipe(
tap((fso: FileSystemObject) => node.children = fso.children),
map((fso: FileSystemObject) => fso.children.filter(x => x.type === FileSystemObjectTypes.DIRECTORY))
);
}
else {
return of(node.children.filter(x => x.type === FileSystemObjectTypes.DIRECTORY))
}
}

getTree(): void {
this.fileService.getFiles().subscribe(fso => {
this.fillSubfolders(fso);
this.fileService.getFiles(FileService.rootUrl, 1).subscribe(fso => {
this.tree = fso.children
});
}

fillSubfolders(fso: FileSystemObject) {
fso.subfolders = fso.children.filter(x => x.type === FileSystemObjectTypes.DIRECTORY);
fso.subfolders.forEach(x => this.fillSubfolders(x));
}

ngOnInit() {
this.getTree();
}

onSelect(fso: FileSystemObject) {
this.selectedObject = fso;
this.onSelected.emit(fso);
this.getChildren(fso).subscribe(() => {
this.selectedObject = fso;
this.onSelected.emit(fso);
})
}
}
30 changes: 21 additions & 9 deletions ShowPics/Controllers/FilesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,25 @@ public FilesController(IOptions<FolderSettings> options, IFilesData filesData, P
}

[HttpGet("{*path}")]
public ActionResult Get(string path)
public ActionResult Get(string path, int depth = 1)
{
var folders = _filesData.GetAll();

if (string.IsNullOrEmpty(path))
{
var folders = _filesData.GetTopLevelFolders(depth, Math.Max(depth - 1, 0));
return Ok(new DirectoryDto()
{
Name = "",
Path = "/",
Children = folders.OrderBy(x => x.Name).Where(x => x.ParentId == null).Select(x => MapToDto(x)).ToList()
Path = _pathHelper.PathToUrl(_options.Value.OriginalsLogicalPrefix),
ApiPath = _pathHelper.PathToUrl(_pathHelper.GetApiPath(_options.Value.OriginalsLogicalPrefix)),
HasSubdirectories = folders.Any(),
Children = depth == 0 ? null : folders.OrderBy(x => x.Name).Select(x => MapToDto(x, depth - 1)).ToList()
});
}
else
{
var folder = folders.SingleOrDefault(x => x.Path == _pathHelper.JoinLogicalPaths(_options.Value.OriginalsLogicalPrefix, path));
var folder = _filesData.GetFolder(_pathHelper.JoinLogicalPaths(_options.Value.OriginalsLogicalPrefix, path), depth + 1, depth);
if (folder != null)
return Ok(MapToDto(folder));
return Ok(MapToDto(folder, depth));
var file = _filesData.GetFile(_pathHelper.JoinLogicalPaths(_options.Value.OriginalsLogicalPrefix, path));
if (file != null)
return Ok(MapToDto(file));
Expand All @@ -66,19 +67,30 @@ FileSystemObject MapToDto(Entities.File file)
ContentType = _mimeTypeMapping.GetValueOrDefault(Path.GetExtension(file.Name)),
Name = file.Name,
Path = _pathHelper.PathToUrl(file.Path),
ApiPath = _pathHelper.PathToUrl(_pathHelper.GetApiPath(file.Path)),
Height = file.Height,
Width = file.Width,
ThumbnailPath = _pathHelper.PathToUrl(file.ThumbnailPath)
};
}

FileSystemObject MapToDto(Entities.Folder folder)
FileSystemObject MapToDto(Entities.Folder folder, int? depth)
{
return new DirectoryDto()
{
Path = _pathHelper.PathToUrl(folder.Path),
ApiPath = _pathHelper.PathToUrl(_pathHelper.GetApiPath(folder.Path)),
Name = folder.Name,
Children = folder.Children.OrderBy(x => x.Name).Select(MapToDto).Union(folder.Files.OrderBy(x => x.OriginalCreationTime ?? x.ModificationTimestamp).Select(MapToDto)).ToList()
HasSubdirectories = folder.Children.Any(),
Children = depth == 0
? null
: folder.Children.OrderBy(x => x.Name)
.Select(x => MapToDto(x, depth - 1))
.Union(
folder.Files.OrderBy(x => x.OriginalCreationTime ?? x.ModificationTimestamp)
.Select(MapToDto)
)
.ToList()
};
}
}
Expand Down

0 comments on commit 7152013

Please sign in to comment.