Sentry Integration (#212)

* Fixed a parsing case

* Integrated Sentry into the solution with anonymous users. Fixed some parsing issues and added BuildInfo into a separate project.

* Fixed some bad parser regex

* Removed bad reference to NLog

* Cleanup of some files not needed
This commit is contained in:
Joseph Milazzo 2021-05-11 14:45:18 -05:00 committed by GitHub
parent 6fc5e535df
commit c8adaee3eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 463 additions and 29 deletions

33
.github/workflows/nightly-docker.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: ci
on:
push:
branches:
- 'master'
jobs:
docker:
runs-on: ubuntu-latest
steps:
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
push: true
tags: user/app:latest
-
name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

View File

@ -61,7 +61,6 @@ namespace API.Tests.Parser
[InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "1")] [InlineData("[Hidoi]_Amaenaideyo_MS_vol01_chp02.rar", "1")]
[InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "4")] [InlineData("NEEDLESS_Vol.4_-_Simeon_6_v2_[SugoiSugoi].rar", "4")]
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "1")] [InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "1")]
public void ParseVolumeTest(string filename, string expected) public void ParseVolumeTest(string filename, string expected)
{ {
Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename)); Assert.Equal(expected, API.Parser.Parser.ParseVolume(filename));
@ -137,6 +136,7 @@ namespace API.Tests.Parser
[InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "Okusama wa Shougakusei")] [InlineData("Okusama wa Shougakusei c003 (v01) [bokuwaNEET]", "Okusama wa Shougakusei")]
[InlineData("VanDread-v01-c001[MD].zip", "VanDread")] [InlineData("VanDread-v01-c001[MD].zip", "VanDread")]
[InlineData("Momo The Blood Taker - Chapter 027 Violent Emotion.cbz", "Momo The Blood Taker")] [InlineData("Momo The Blood Taker - Chapter 027 Violent Emotion.cbz", "Momo The Blood Taker")]
[InlineData("Green Worldz - Chapter 112 Final Chapter (End).cbz", "Green Worldz")]
public void ParseSeriesTest(string filename, string expected) public void ParseSeriesTest(string filename, string expected)
{ {
Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename)); Assert.Equal(expected, API.Parser.Parser.ParseSeries(filename));
@ -225,6 +225,7 @@ namespace API.Tests.Parser
[InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter", true)] [InlineData("Corpse Party -The Anthology- Sachikos game of love Hysteric Birthday 2U Extra Chapter", true)]
[InlineData("Ani-Hina Art Collection.cbz", true)] [InlineData("Ani-Hina Art Collection.cbz", true)]
[InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", true)] [InlineData("Gifting The Wonderful World With Blessings! - 3 Side Stories [yuNS][Unknown]", true)]
[InlineData("Yuki Merry - 4-Komga Anthology", true)]
public void ParseMangaSpecialTest(string input, bool expected) public void ParseMangaSpecialTest(string input, bool expected)
{ {
Assert.Equal(expected, !string.IsNullOrEmpty(API.Parser.Parser.ParseMangaSpecial(input))); Assert.Equal(expected, !string.IsNullOrEmpty(API.Parser.Parser.ParseMangaSpecial(input)));

View File

@ -12,6 +12,23 @@
<ApplicationIcon>../favicon.ico</ApplicationIcon> <ApplicationIcon>../favicon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<!-- Set the Product and Version info for our own projects -->
<PropertyGroup>
<Product>Kavita</Product>
<Company>kareadita.github.io</Company>
<Copyright>Copyright 2020-$([System.DateTime]::Now.ToString('yyyy')) kareadita.github.io (GNU General Public v3)</Copyright>
<!-- Should be replaced by CI -->
<AssemblyVersion>0.4.1</AssemblyVersion>
<AssemblyConfiguration>$(Configuration)-dev</AssemblyConfiguration>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<Deterministic Condition="$(AssemblyVersion.EndsWith('*'))">False</Deterministic>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" /> <PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="8.1.1" />
<PackageReference Include="ExCSS" Version="4.1.0" /> <PackageReference Include="ExCSS" Version="4.1.0" />
@ -33,6 +50,7 @@
<PackageReference Include="NetVips" Version="2.0.0" /> <PackageReference Include="NetVips" Version="2.0.0" />
<PackageReference Include="NetVips.Native" Version="8.10.6" /> <PackageReference Include="NetVips.Native" Version="8.10.6" />
<PackageReference Include="NReco.Logging.File" Version="1.1.1" /> <PackageReference Include="NReco.Logging.File" Version="1.1.1" />
<PackageReference Include="Sentry.AspNetCore" Version="3.3.4" />
<PackageReference Include="SharpCompress" Version="0.28.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="8.20.0.28934"> <PackageReference Include="SonarAnalyzer.CSharp" Version="8.20.0.28934">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
@ -65,4 +83,8 @@
<_ContentIncludedByDefault Remove="logs\kavita.json" /> <_ContentIncludedByDefault Remove="logs\kavita.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Kavita.Common\Kavita.Common.csproj" />
</ItemGroup>
</Project> </Project>

View File

@ -1,4 +1,5 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.DTOs; using API.DTOs;
using API.Entities; using API.Entities;
@ -148,6 +149,7 @@ namespace API.Controllers
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0, int limit = 20) public async Task<ActionResult<IEnumerable<SeriesDto>>> GetRecentlyAdded(int libraryId = 0, int limit = 20)
{ {
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Ok(Array.Empty<SeriesDto>());
return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(user.Id, libraryId, limit)); return Ok(await _unitOfWork.SeriesRepository.GetRecentlyAdded(user.Id, libraryId, limit));
} }
@ -155,6 +157,7 @@ namespace API.Controllers
public async Task<ActionResult<IEnumerable<SeriesDto>>> GetInProgress(int libraryId = 0, int limit = 20) public async Task<ActionResult<IEnumerable<SeriesDto>>> GetInProgress(int libraryId = 0, int limit = 20)
{ {
var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername()); var user = await _unitOfWork.UserRepository.GetUserByUsernameAsync(User.GetUsername());
if (user == null) return Ok(Array.Empty<SeriesDto>());
return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit)); return Ok(await _unitOfWork.SeriesRepository.GetInProgress(user.Id, libraryId, limit));
} }

View File

@ -1,20 +1,40 @@
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base #This Dockerfile pulls the latest git commit and builds Kavita from source
WORKDIR /app FROM mcr.microsoft.com/dotnet/sdk:5.0-focal AS builder
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build ENV DEBIAN_FRONTEND=noninteractive
WORKDIR /src ARG TARGETPLATFORM
COPY ["API/API.csproj", "API/"]
RUN dotnet restore "API/API.csproj"
COPY . .
WORKDIR "/src/API"
RUN dotnet build "API.csproj" -c Release -o /app/build
FROM build AS publish #Installs nodejs and npm
RUN dotnet publish "API.csproj" -c Release -o /app/publish RUN curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/lib/apt/lists/*
FROM base AS final #Builds app based on platform
WORKDIR /app COPY build_target.sh /build_target.sh
COPY --from=publish /app/publish . RUN /build_target.sh
ENTRYPOINT ["dotnet", "API.dll"]
#Production image
FROM ubuntu:focal
#Move the output files to where they need to be
COPY --from=builder /Projects/Kavita/_output/build/Kavita /kavita
#Installs program dependencies
RUN apt-get update \
&& apt-get install -y libicu-dev libssl1.1 pwgen \
&& rm -rf /var/lib/apt/lists/*
#Creates the manga storage directory
RUN mkdir /manga /kavita/data
RUN cp /kavita/appsettings.Development.json /kavita/appsettings.json \
&& sed -i 's/Data source=kavita.db/Data source=data\/kavita.db/g' /kavita/appsettings.json
COPY entrypoint.sh /entrypoint.sh
EXPOSE 5000
WORKDIR /kavita
ENTRYPOINT ["/bin/bash"]
CMD ["/entrypoint.sh"]

View File

@ -92,7 +92,7 @@ namespace API.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Momo The Blood Taker - Chapter 027 Violent Emotion.cbz // Momo The Blood Taker - Chapter 027 Violent Emotion.cbz
new Regex( new Regex(
@"(?<Series>.*) (\b|_|-)(?:chapter)", @"(?<Series>.*)(\b|_|-|\s)(?:chapter)(\b|_|-|\s)\d",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb) // Historys Strongest Disciple Kenichi_v11_c90-98.zip, Killing Bites Vol. 0001 Ch. 0001 - Galactica Scanlations (gb)
new Regex( new Regex(
@ -101,7 +101,7 @@ namespace API.Parser
//Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip must be before [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip //Ichinensei_ni_Nacchattara_v01_ch01_[Taruby]_v1.1.zip must be before [Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
// due to duplicate version identifiers in file. // due to duplicate version identifiers in file.
new Regex( new Regex(
@"(?<Series>.*)(v|s)\d+(-\d+)?(_| )", @"(?<Series>.*)(v|s)\d+(-\d+)?(_|\s)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
//[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip //[Suihei Kiki]_Kasumi_Otoko_no_Ko_[Taruby]_v1.1.zip
new Regex( new Regex(
@ -121,7 +121,7 @@ namespace API.Parser
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Tonikaku Kawaii (Ch 59-67) (Ongoing) // Tonikaku Kawaii (Ch 59-67) (Ongoing)
new Regex( new Regex(
@"(?<Series>.*)( |_)\((c |ch |chapter )", @"(?<Series>.*)(\s|_)\((c\s|ch\s|chapter\s)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
// Black Bullet (This is very loose, keep towards bottom) // Black Bullet (This is very loose, keep towards bottom)
new Regex( new Regex(
@ -364,7 +364,7 @@ namespace API.Parser
{ {
// All Keywords, does not account for checking if contains volume/chapter identification. Parser.Parse() will handle. // All Keywords, does not account for checking if contains volume/chapter identification. Parser.Parse() will handle.
new Regex( new Regex(
@"(?<Special>Specials?|OneShot|One\-Shot|Omake|Extra( Chapter)?|Art Collection|Side( |_)Stories)", @"(?<Special>Specials?|OneShot|One\-Shot|Omake|Extra( Chapter)?|Art Collection|Side( |_)Stories|(?<!The\s)Anthology)",
RegexOptions.IgnoreCase | RegexOptions.Compiled), RegexOptions.IgnoreCase | RegexOptions.Compiled),
}; };

View File

@ -1,7 +1,11 @@
using System; using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using API.Data; using API.Data;
using API.Entities; using API.Entities;
using Kavita.Common;
using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.AspNetCore.Server.Kestrel.Core;
@ -9,18 +13,22 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Sentry;
namespace API namespace API
{ {
public class Program public class Program
{ {
private static readonly int HttpPort = 5000; private static readonly int HttpPort = 5000;
protected Program() protected Program()
{ {
} }
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
// Before anything, check if JWT has been generated properly or if user still has default
var host = CreateHostBuilder(args).Build(); var host = CreateHostBuilder(args).Build();
using var scope = host.Services.CreateScope(); using var scope = host.Services.CreateScope();
@ -55,6 +63,36 @@ namespace API
options.Protocols = HttpProtocols.Http1AndHttp2; options.Protocols = HttpProtocols.Http1AndHttp2;
}); });
}); });
webBuilder.UseSentry(options =>
{
options.Dsn = "https://40f4e7b49c094172a6f99d61efb2740f@o641015.ingest.sentry.io/5757423";
options.MaxBreadcrumbs = 200;
options.AttachStacktrace = true;
options.Debug = false;
options.SendDefaultPii = false;
options.DiagnosticLevel = SentryLevel.Debug;
options.ShutdownTimeout = TimeSpan.FromSeconds(5);
options.Release = BuildInfo.Version.ToString();
options.AddExceptionFilterForType<OutOfMemoryException>();
options.AddExceptionFilterForType<NetVips.VipsException>();
options.AddExceptionFilterForType<InvalidDataException>();
options.ConfigureScope(scope =>
{
scope.User = new User()
{
Id = HashUtil.AnonymousToken()
};
scope.Contexts.App.Name = BuildInfo.AppName;
scope.Contexts.App.Version = BuildInfo.Version.ToString();
scope.Contexts.App.StartTime = DateTime.UtcNow;
scope.Contexts.App.Hash = HashUtil.AnonymousToken();
scope.Contexts.App.Build = BuildInfo.Release;
scope.SetTag("culture", Thread.CurrentThread.CurrentCulture.Name);
scope.SetTag("branch", BuildInfo.Branch);
});
});
webBuilder.UseStartup<Startup>(); webBuilder.UseStartup<Startup>();
}); });
} }

View File

@ -1,12 +1,14 @@
using System; using System;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Reflection;
using API.Extensions; using API.Extensions;
using API.Interfaces; using API.Interfaces;
using API.Middleware; using API.Middleware;
using API.Services; using API.Services;
using Hangfire; using Hangfire;
using Hangfire.MemoryStorage; using Hangfire.MemoryStorage;
using Kavita.Common.EnvironmentInfo;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
@ -131,7 +133,7 @@ namespace API
applicationLifetime.ApplicationStopping.Register(OnShutdown); applicationLifetime.ApplicationStopping.Register(OnShutdown);
applicationLifetime.ApplicationStarted.Register(() => applicationLifetime.ApplicationStarted.Register(() =>
{ {
Console.WriteLine("Kavita - v0.4.1"); Console.WriteLine("Kavita - v" + BuildInfo.Version);
}); });
// Any services that should be bootstrapped go here // Any services that should be bootstrapped go here
@ -140,10 +142,10 @@ namespace API
private void OnShutdown() private void OnShutdown()
{ {
Console.WriteLine("Server is shutting down. Going to dispose Hangfire"); Console.WriteLine("Server is shutting down. Please allow a few seconds to stop any background jobs...");
//this code is called when the application stops
TaskScheduler.Client.Dispose(); TaskScheduler.Client.Dispose();
System.Threading.Thread.Sleep(1000); System.Threading.Thread.Sleep(1000);
Console.WriteLine("You may now close the application window.");
} }
} }
} }

View File

@ -0,0 +1,56 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
namespace Kavita.Common.EnvironmentInfo
{
public static class BuildInfo
{
static BuildInfo()
{
var assembly = Assembly.GetExecutingAssembly();
Version = assembly.GetName().Version;
var attributes = assembly.GetCustomAttributes(true);
Branch = "unknown";
var config = attributes.OfType<AssemblyConfigurationAttribute>().FirstOrDefault();
if (config != null)
{
Branch = config.Configuration;
}
Release = $"{Version}-{Branch}";
}
public static string AppName { get; } = "Kavita";
public static Version Version { get; }
public static string Branch { get; }
public static string Release { get; }
public static DateTime BuildDateTime
{
get
{
var fileLocation = Assembly.GetCallingAssembly().Location;
return new FileInfo(fileLocation).LastWriteTimeUtc;
}
}
public static bool IsDebug
{
get
{
#if DEBUG
return true;
#else
return false;
#endif
}
}
}
}

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
namespace Kavita.Common.EnvironmentInfo
{
public class OsInfo : IOsInfo
{
public static Os Os { get; }
public static bool IsNotWindows => !IsWindows;
public static bool IsLinux => Os == Os.Linux || Os == Os.LinuxMusl || Os == Os.Bsd;
public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows;
// this needs to not be static so we can mock it
public bool IsDocker { get; }
public string Version { get; }
public string Name { get; }
public string FullName { get; }
static OsInfo()
{
var platform = Environment.OSVersion.Platform;
switch (platform)
{
case PlatformID.Win32NT:
{
Os = Os.Windows;
break;
}
case PlatformID.MacOSX:
case PlatformID.Unix:
{
Os = GetPosixFlavour();
break;
}
}
}
public OsInfo(IEnumerable<IOsVersionAdapter> versionAdapters)
{
OsVersionModel osInfo = null;
foreach (var osVersionAdapter in versionAdapters.Where(c => c.Enabled))
{
try
{
osInfo = osVersionAdapter.Read();
}
catch (Exception e)
{
Console.WriteLine("Couldn't get OS Version info: " + e.Message);
}
if (osInfo != null)
{
break;
}
}
if (osInfo != null)
{
Name = osInfo.Name;
Version = osInfo.Version;
FullName = osInfo.FullName;
}
else
{
Name = Os.ToString();
FullName = Name;
}
if (IsLinux && File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
{
IsDocker = true;
}
}
private static Os GetPosixFlavour()
{
var output = RunAndCapture("uname", "-s");
if (output.StartsWith("Darwin"))
{
return Os.Osx;
}
else if (output.Contains("BSD"))
{
return Os.Bsd;
}
else
{
#if ISMUSL
return Os.LinuxMusl;
#else
return Os.Linux;
#endif
}
}
private static string RunAndCapture(string filename, string args)
{
var p = new Process
{
StartInfo =
{
FileName = filename,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true
}
};
p.Start();
// To avoid deadlocks, always read the output stream first and then wait.
var output = p.StandardOutput.ReadToEnd();
p.WaitForExit(1000);
return output;
}
}
public interface IOsInfo
{
string Version { get; }
string Name { get; }
string FullName { get; }
bool IsDocker { get; }
}
public enum Os
{
Windows,
Linux,
Osx,
LinuxMusl,
Bsd
}
}

View File

@ -0,0 +1,8 @@
namespace Kavita.Common.EnvironmentInfo
{
public interface IOsVersionAdapter
{
bool Enabled { get; }
OsVersionModel Read();
}
}

View File

@ -0,0 +1,27 @@
namespace Kavita.Common.EnvironmentInfo
{
public class OsVersionModel
{
public OsVersionModel(string name, string version, string fullName = null)
{
Name = Trim(name);
Version = Trim(version);
if (string.IsNullOrWhiteSpace(fullName))
{
fullName = $"{Name} {Version}";
}
FullName = Trim(fullName);
}
private static string Trim(string source)
{
return source.Trim().Trim('"', '\'');
}
public string Name { get; }
public string FullName { get; }
public string Version { get; }
}
}

37
Kavita.Common/HashUtil.cs Normal file
View File

@ -0,0 +1,37 @@
using System;
using System.Text;
namespace Kavita.Common
{
public static class HashUtil
{
public static string CalculateCrc(string input)
{
uint mCrc = 0xffffffff;
byte[] bytes = Encoding.UTF8.GetBytes(input);
foreach (byte myByte in bytes)
{
mCrc ^= (uint)myByte << 24;
for (var i = 0; i < 8; i++)
{
if ((Convert.ToUInt32(mCrc) & 0x80000000) == 0x80000000)
{
mCrc = (mCrc << 1) ^ 0x04C11DB7;
}
else
{
mCrc <<= 1;
}
}
}
return $"{mCrc:x8}";
}
public static string AnonymousToken()
{
var seed = $"{Environment.ProcessorCount}_{Environment.OSVersion.Platform}_{Environment.MachineName}_{Environment.UserName}";
return HashUtil.CalculateCrc(seed);
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<Company>kareadita.github.io</Company>
<Product>Kavita</Product>
<AssemblyVersion>0.4.1</AssemblyVersion>
<NeutralLanguage>en</NeutralLanguage>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Sentry" Version="3.3.4" />
</ItemGroup>
<ItemGroup>
<Reference Include="JetBrains.ReSharper.TestRunner.Merged, Version=1.3.1.55, Culture=neutral, PublicKeyToken=5c492ec4f3eccde3">
<HintPath>D:\Program Files\JetBrains\JetBrains Rider 2020.3.2\lib\ReSharperHost\TestRunner\netcoreapp2.0\JetBrains.ReSharper.TestRunner.Merged.dll</HintPath>
</Reference>
<Reference Include="Microsoft.Win32.Registry, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<HintPath>..\..\..\..\..\..\..\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.5\Microsoft.Win32.Registry.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

View File

@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API", "API\API.csproj", "{1
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API.Tests", "API.Tests\API.Tests.csproj", "{6F7910F2-1B95-4570-A490-519C8935B9D1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "API.Tests", "API.Tests\API.Tests.csproj", "{6F7910F2-1B95-4570-A490-519C8935B9D1}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kavita.Common", "Kavita.Common\Kavita.Common.csproj", "{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -44,5 +46,17 @@ Global
{6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x64.Build.0 = Release|Any CPU {6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x64.Build.0 = Release|Any CPU
{6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x86.ActiveCfg = Release|Any CPU {6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x86.ActiveCfg = Release|Any CPU
{6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x86.Build.0 = Release|Any CPU {6F7910F2-1B95-4570-A490-519C8935B9D1}.Release|x86.Build.0 = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|x64.ActiveCfg = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|x64.Build.0 = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|x86.ActiveCfg = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Debug|x86.Build.0 = Debug|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|Any CPU.Build.0 = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|x64.ActiveCfg = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|x64.Build.0 = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|x86.ActiveCfg = Release|Any CPU
{165A86F5-9E74-4C05-9305-A6F0BA32C9EE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -10,12 +10,13 @@ your manga collection with your friends and family!
## Goals: ## Goals:
* Serve up Manga (cbr, cbz, zip/rar, raw images) and Books (epub, mobi, azw, djvu, pdf) * Serve up Manga/Webtoons/Comics (cbr, cbz, zip/rar, raw images) and Books (epub, mobi, azw, djvu, pdf)
* Provide Reader for Manga and Books (Light Novels) via web app that is responsive * Provide Readers via web app that is responsive
* Provide customization themes (server installed) for web app * Provide a dark theme for web app
* Provide hooks into metadata providers to fetch Manga data * Provide hooks into metadata providers to fetch Manga data
* Metadata should allow for collections, want to read integration from 3rd party services, genres. * Metadata should allow for collections, want to read integration from 3rd party services, genres.
* Ability to manage users, access, and ratings * Ability to manage users, access, and ratings
* Ability to sync ratings and reviews to external services
## How to Build ## How to Build
- Ensure you've cloned Kavita-webui. You should have Projects/Kavita and Projects/Kavita-webui - Ensure you've cloned Kavita-webui. You should have Projects/Kavita and Projects/Kavita-webui