Browse Source

#560 Allow to use file system as data source for docs module

pull/626/head
Halil ibrahim Kalkan 8 years ago
parent
commit
b371a58627
  1. 23
      framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
  2. 25
      framework/src/Volo.Abp.Http/Volo/Abp/Http/MimeTypes.cs
  3. 15
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/IO/FileHelper_Tests.cs
  4. 7
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentResourceDto.cs
  5. 1
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs
  6. 1
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs
  7. 18
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs
  8. 1
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs
  9. 4
      modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs
  10. 1
      modules/docs/src/Volo.Docs.Application/Volo/Docs/DocsApplicationAutoMapperProfile.cs
  11. 14
      modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs
  12. 23
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/ResourceNotFoundException.cs
  13. 2
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs
  14. 12
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/DocumentResource.cs
  15. 4
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs
  16. 49
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs
  17. 33
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Projects/ProjectFileSystemExtensions.cs
  18. 81
      modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs
  19. 11
      modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs
  20. 36
      modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentResourceController.cs
  21. 2
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml
  22. 39
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

23
framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs

@ -1,4 +1,5 @@
using System.IO;
using JetBrains.Annotations;
namespace Volo.Abp.IO
{
@ -18,5 +19,27 @@ namespace Volo.Abp.IO
File.Delete(filePath);
}
}
/// <summary>
/// Gets extension of a file.
/// </summary>
/// <param name="fileNameWithExtension"></param>
/// <returns>
/// Returns extension without dot.
/// Returns null if given <paramref name="fileNameWithExtension"></paramref> does not include dot.
/// </returns>
[CanBeNull]
public static string GetExtension([NotNull] string fileNameWithExtension)
{
Check.NotNull(fileNameWithExtension, nameof(fileNameWithExtension));
var lastDotIndex = fileNameWithExtension.LastIndexOf('.');
if (lastDotIndex < 0)
{
return null;
}
return fileNameWithExtension.Substring(lastDotIndex + 1);
}
}
}

25
framework/src/Volo.Abp.Http/Volo/Abp/Http/MimeTypes.cs

@ -1,4 +1,6 @@
namespace Volo.Abp.Http
using System;
namespace Volo.Abp.Http
{
/* Taken from https://gist.github.com/markwhitaker/b29c0142360714688a7cf863ab33e5c9 */
@ -83,5 +85,26 @@
public const string Quicktime = "video/quicktime";
public const string Webm = "video/webm";
}
public static string GetByExtension(string extension)
{
extension = extension.RemovePreFix(".").ToLowerInvariant();
switch (extension)
{
case "png":
return Image.Png;
case "gif":
return Image.Gif;
case "jpg":
case "jpeg":
return Image.Jpeg;
//TODO: Add other extensions too..
default:
return Application.OctetStream;
}
}
}
}

15
framework/test/Volo.Abp.Core.Tests/Volo/Abp/IO/FileHelper_Tests.cs

@ -0,0 +1,15 @@
using Shouldly;
using Xunit;
namespace Volo.Abp.IO
{
public class FileHelper_Tests
{
[Fact]
public void GetExtension()
{
FileHelper.GetExtension("test").ShouldBeNull();
FileHelper.GetExtension("te.st").ShouldBe("st");
}
}
}

7
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/DocumentResourceDto.cs

@ -0,0 +1,7 @@
namespace Volo.Docs.Documents
{
public class DocumentResourceDto
{
public byte[] Content { get; set; }
}
}

1
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDefaultDocumentInput.cs

@ -8,7 +8,6 @@ namespace Volo.Docs.Documents
{
public Guid ProjectId { get; set; }
[Required]
[StringLength(ProjectConsts.MaxVersionNameLength)]
public string Version { get; set; }
}

1
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentInput.cs

@ -12,7 +12,6 @@ namespace Volo.Docs.Documents
[StringLength(DocumentConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[StringLength(ProjectConsts.MaxVersionNameLength)]
public string Version { get; set; }
}

18
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetDocumentResourceInput.cs

@ -0,0 +1,18 @@
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Docs.Projects;
namespace Volo.Docs.Documents
{
public class GetDocumentResourceInput
{
public Guid ProjectId { get; set; }
[Required]
[StringLength(DocumentConsts.MaxNameLength)]
public string Name { get; set; }
[StringLength(ProjectConsts.MaxVersionNameLength)]
public string Version { get; set; }
}
}

1
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/GetNavigationDocumentInput.cs

@ -8,7 +8,6 @@ namespace Volo.Docs.Documents
{
public Guid ProjectId { get; set; }
[Required]
[StringLength(ProjectConsts.MaxVersionNameLength)]
public string Version { get; set; }
}

4
modules/docs/src/Volo.Docs.Application.Contracts/Volo/Docs/Documents/IDocumentAppService.cs

@ -9,6 +9,8 @@ namespace Volo.Docs.Documents
Task<DocumentWithDetailsDto> GetDefaultAsync(GetDefaultDocumentInput input);
Task<DocumentWithDetailsDto> GetNavigationDocumentAsync(GetNavigationDocumentInput input);
Task<DocumentWithDetailsDto> GetNavigationAsync(GetNavigationDocumentInput input);
Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input);
}
}

1
modules/docs/src/Volo.Docs.Application/Volo/Docs/DocsApplicationAutoMapperProfile.cs

@ -13,6 +13,7 @@ namespace Volo.Docs
CreateMap<VersionInfo, VersionInfoDto>();
CreateMap<Document, DocumentWithDetailsDto>()
.Ignore(x => x.Project);
CreateMap<DocumentResource, DocumentResourceDto>();
}
}
}

14
modules/docs/src/Volo.Docs.Application/Volo/Docs/Documents/DocumentAppService.cs

@ -39,7 +39,7 @@ namespace Volo.Docs.Documents
);
}
public virtual async Task<DocumentWithDetailsDto> GetNavigationDocumentAsync(GetNavigationDocumentInput input)
public virtual async Task<DocumentWithDetailsDto> GetNavigationAsync(GetNavigationDocumentInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
@ -50,13 +50,23 @@ namespace Volo.Docs.Documents
);
}
public async Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input)
{
var project = await _projectRepository.GetAsync(input.ProjectId);
var store = _documentStoreFactory.Create(project.DocumentStoreType);
var documentResource = await store.GetResource(project, input.Name, input.Version);
return ObjectMapper.Map<DocumentResource, DocumentResourceDto>(documentResource);
}
protected virtual async Task<DocumentWithDetailsDto> GetDocumentWithDetailsDto(
Project project,
string documentName,
string version)
{
var store = _documentStoreFactory.Create(project.DocumentStoreType);
var document = await store.FindDocument(project, documentName, version);
var document = await store.GetDocument(project, documentName, version);
return CreateDocumentWithDetailsDto(project, document);
}

23
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/ResourceNotFoundException.cs

@ -0,0 +1,23 @@
using System;
using System.Runtime.Serialization;
using Volo.Abp;
namespace Volo.Docs
{
[Serializable]
public class ResourceNotFoundException : BusinessException
{
public string ResourceName { get; set; }
public ResourceNotFoundException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
public ResourceNotFoundException(string resourceName)
{
ResourceName = resourceName;
}
}
}

2
modules/docs/src/Volo.Docs.Domain/Volo/Docs/DocsDomainModule.cs

@ -3,6 +3,7 @@ using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Volo.Docs.Documents;
using Volo.Docs.FileSystem.Documents;
using Volo.Docs.GitHub.Documents;
using Volo.Docs.Localization;
@ -32,6 +33,7 @@ namespace Volo.Docs
Configure<DocumentStoreOptions>(options =>
{
options.Stores[GithubDocumentStore.Type] = typeof(GithubDocumentStore);
options.Stores[FileSystemDocumentStore.Type] = typeof(FileSystemDocumentStore);
});
}
}

12
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/DocumentResource.cs

@ -0,0 +1,12 @@
namespace Volo.Docs.Documents
{
public class DocumentResource
{
public byte[] Content { get; }
public DocumentResource(byte[] content)
{
Content = content;
}
}
}

4
modules/docs/src/Volo.Docs.Domain/Volo/Docs/Documents/IDocumentStore.cs

@ -7,8 +7,10 @@ namespace Volo.Docs.Documents
{
public interface IDocumentStore : IDomainService
{
Task<Document> FindDocument(Project project, string documentName, string version);
Task<Document> GetDocument(Project project, string documentName, string version);
Task<List<VersionInfo>> GetVersions(Project project);
Task<DocumentResource> GetResource(Project project, string resourceName, string version);
}
}

49
modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Documents/FileSystemDocumentStore.cs

@ -0,0 +1,49 @@
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using Volo.Abp.Domain.Services;
using Volo.Docs.Documents;
using Volo.Docs.FileSystem.Projects;
using Volo.Docs.Projects;
namespace Volo.Docs.FileSystem.Documents
{
public class FileSystemDocumentStore : DomainService, IDocumentStore
{
public const string Type = "FileSystem";
public async Task<Document> GetDocument(Project project, string documentName, string version)
{
var path = Path.Combine(project.GetFileSystemPath(), documentName);
var content = File.ReadAllText(path); //TODO: async!
var localDirectory = "";
if (documentName.Contains("/"))
{
localDirectory = documentName.Substring(0, documentName.LastIndexOf('/'));
}
return new Document
{
Content = content,
FileName = Path.GetFileName(path),
Format = project.Format,
LocalDirectory = localDirectory,
Title = documentName,
RawRootUrl = $"/document-resources?projectId={project.Id.ToString()}&version={version}&name=",
RootUrl = "/"
};
}
public Task<List<VersionInfo>> GetVersions(Project project)
{
return Task.FromResult(new List<VersionInfo>());
}
public async Task<DocumentResource> GetResource(Project project, string resourceName, string version)
{
var path = Path.Combine(project.GetFileSystemPath(), resourceName);
return new DocumentResource(File.ReadAllBytes(path));
}
}
}

33
modules/docs/src/Volo.Docs.Domain/Volo/Docs/FileSystem/Projects/ProjectFileSystemExtensions.cs

@ -0,0 +1,33 @@
using System;
using JetBrains.Annotations;
using Volo.Abp;
using Volo.Docs.FileSystem.Documents;
using Volo.Docs.Projects;
namespace Volo.Docs.FileSystem.Projects
{
public static class ProjectFileSystemExtensions
{
public static string GetFileSystemPath([NotNull] this Project project)
{
CheckFileSystemProject(project);
return project.ExtraProperties["Path"] as string;
}
public static void SetFileSystemPath([NotNull] this Project project, string value)
{
CheckFileSystemProject(project);
project.ExtraProperties["Path"] = value;
}
private static void CheckFileSystemProject(Project project)
{
Check.NotNull(project, nameof(project));
if (project.DocumentStoreType != FileSystemDocumentStore.Type)
{
throw new ApplicationException("Given project has not a FileSystem document store!");
}
}
}
}

81
modules/docs/src/Volo.Docs.Domain/Volo/Docs/GitHub/Documents/GithubDocumentStore.cs

@ -15,11 +15,13 @@ using Project = Volo.Docs.Projects.Project;
namespace Volo.Docs.GitHub.Documents
{
//TODO: Needs refactoring
public class GithubDocumentStore : DomainService, IDocumentStore
{
public const string Type = "GitHub";
public virtual async Task<Document> FindDocument(Project project, string documentName, string version)
public virtual async Task<Document> GetDocument(Project project, string documentName, string version)
{
var rootUrl = project.GetGitHubUrl(version);
var rawRootUrl = CalculateRawRootUrl(rootUrl);
@ -55,28 +57,6 @@ namespace Volo.Docs.GitHub.Documents
.ReplaceFirst("/tree/", "/");
}
private async Task<string> DownloadWebContentAsync(string rawUrl, string token)
{
try
{
using (var webClient = new WebClient())
{
if (!token.IsNullOrWhiteSpace())
{
webClient.Headers.Add("Authorization", "token " + token);
}
return await webClient.DownloadStringTaskAsync(new Uri(rawUrl));
}
}
catch (Exception ex)
{
//TODO: Only handle when document is really not available
Logger.LogWarning(ex.Message, ex);
throw new DocumentNotFoundException(rawUrl);
}
}
public async Task<List<VersionInfo>> GetVersions(Project project)
{
try
@ -97,6 +77,17 @@ namespace Volo.Docs.GitHub.Documents
}
}
public async Task<DocumentResource> GetResource(Project project, string resourceName, string version)
{
var rawRootUrl = CalculateRawRootUrl(project.GetGitHubUrl(version));
var content = await DownloadWebContentAsByteArrayAsync(
rawRootUrl + resourceName,
project.GetGitHubAccessTokenOrNull()
);
return new DocumentResource(content);
}
private async Task<IReadOnlyList<Release>> GetReleasesAsync(Project project)
{
var url = project.GetGitHubUrl();
@ -144,5 +135,49 @@ namespace Volo.Docs.GitHub.Documents
throw new Exception($"Github url is not valid: {url}");
}
}
private async Task<string> DownloadWebContentAsync(string rawUrl, string token)
{
try
{
using (var webClient = new WebClient())
{
if (!token.IsNullOrWhiteSpace())
{
webClient.Headers.Add("Authorization", "token " + token);
}
return await webClient.DownloadStringTaskAsync(new Uri(rawUrl));
}
}
catch (Exception ex)
{
//TODO: Only handle when document is really not available
Logger.LogWarning(ex.Message, ex);
throw new DocumentNotFoundException(rawUrl);
}
}
private async Task<byte[]> DownloadWebContentAsByteArrayAsync(string rawUrl, string token)
{
try
{
using (var webClient = new WebClient())
{
if (!token.IsNullOrWhiteSpace())
{
webClient.Headers.Add("Authorization", "token " + token);
}
return await webClient.DownloadDataTaskAsync(new Uri(rawUrl));
}
}
catch (Exception ex)
{
//TODO: Only handle when resource is really not available
Logger.LogWarning(ex.Message, ex);
throw new ResourceNotFoundException(rawUrl);
}
}
}
}

11
modules/docs/src/Volo.Docs.HttpApi/Volo/Docs/Documents/DocsDocumentController.cs

@ -34,9 +34,16 @@ namespace Volo.Docs.Documents
[HttpGet]
[Route("navigation")]
public virtual Task<DocumentWithDetailsDto> GetNavigationDocumentAsync(GetNavigationDocumentInput input)
public virtual Task<DocumentWithDetailsDto> GetNavigationAsync(GetNavigationDocumentInput input)
{
return DocumentAppService.GetNavigationDocumentAsync(input);
return DocumentAppService.GetNavigationAsync(input);
}
[HttpGet]
[Route("resource")]
public Task<DocumentResourceDto> GetResourceAsync(GetDocumentResourceInput input)
{
return DocumentAppService.GetResourceAsync(input);
}
}
}

36
modules/docs/src/Volo.Docs.Web/Areas/Documents/DocumentResourceController.cs

@ -0,0 +1,36 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Http;
using Volo.Abp.IO;
using Volo.Docs.Documents;
namespace Volo.Docs.Areas.Documents
{
[RemoteService]
[Area("docs")]
[ControllerName("DocumentResource")]
[Route("document-resources")]
public class DocumentResourceController : AbpController
{
private readonly IDocumentAppService _documentAppService;
public DocumentResourceController(IDocumentAppService documentAppService)
{
_documentAppService = documentAppService;
}
[HttpGet]
[Route("")]
//[Produces(MimeTypes.Image.Jpeg, MimeTypes.Image.Png, MimeTypes.Image.Gif)]
public async Task<FileResult> GetResource(GetDocumentResourceInput input)
{
input.Name = input.Name.RemovePreFix("/");
var documentResource = await _documentAppService.GetResourceAsync(input);
var contentType = MimeTypes.GetByExtension(FileHelper.GetExtension(input.Name));
return File(documentResource.Content, contentType);
}
}
}

2
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml

@ -105,7 +105,7 @@
else
{
<ul root-node="@Model.Navigation.RootNode"
version="@(Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version)"
version="@(Model.LatestVersionInfo == null || Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version)"
project-name="@Model.ProjectName"
project-format="@Model.Project.Format"
selected-document-name="@Model.DocumentNameWithExtension"

39
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -71,27 +71,30 @@ namespace Volo.Docs.Pages.Documents.Project
.Select(v => new VersionInfoViewModel(v.DisplayName, v.Name))
.ToList();
LatestVersionInfo = versions.First();
LatestVersionInfo.DisplayText = $"{LatestVersionInfo.DisplayText} ({DocsAppConsts.Latest})";
LatestVersionInfo.Version = LatestVersionInfo.Version;
if (string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase))
{
LatestVersionInfo.IsSelected = true;
Version = LatestVersionInfo.Version;
}
else
if (versions.Any())
{
var versionFromUrl = versions.FirstOrDefault(v => v.Version == Version);
if (versionFromUrl != null)
LatestVersionInfo = versions.First();
LatestVersionInfo.DisplayText = $"{LatestVersionInfo.DisplayText} ({DocsAppConsts.Latest})";
LatestVersionInfo.Version = LatestVersionInfo.Version;
if (string.Equals(Version, DocsAppConsts.Latest, StringComparison.OrdinalIgnoreCase))
{
versionFromUrl.IsSelected = true;
Version = versionFromUrl.Version;
LatestVersionInfo.IsSelected = true;
Version = LatestVersionInfo.Version;
}
else
{
versions.First().IsSelected = true;
Version = versions.First().Version;
var versionFromUrl = versions.FirstOrDefault(v => v.Version == Version);
if (versionFromUrl != null)
{
versionFromUrl.IsSelected = true;
Version = versionFromUrl.Version;
}
else
{
versions.First().IsSelected = true;
Version = versions.First().Version;
}
}
}
@ -107,7 +110,7 @@ namespace Volo.Docs.Pages.Documents.Project
{
try
{
var document = await _documentAppService.GetNavigationDocumentAsync(
var document = await _documentAppService.GetNavigationAsync(
new GetNavigationDocumentInput
{
ProjectId = Project.Id,
@ -127,7 +130,7 @@ namespace Volo.Docs.Pages.Documents.Project
public string CreateLink(VersionInfoViewModel latestVersion, string version, string documentName = null)
{
if (latestVersion.Version == version)
if (latestVersion == null || latestVersion.Version == version)
{
version = DocsAppConsts.Latest;
}

Loading…
Cancel
Save