Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 118 additions & 98 deletions doki-theme-visualstudio/AssetManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,108 +2,128 @@
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Shell;
using Task = System.Threading.Tasks.Task;

namespace doki_theme_visualstudio {
public enum AssetCategory {
Stickers,
Backgrounds,
Promotion,
Misc
}

public static class AssetManager {
public const string AssetSource = "https://doki.assets.unthrottled.io";
public const string FallbackAssetSource = "https://raw.githubusercontent.com/doki-theme/doki-theme-assets/master";

public static async Task<string?> ResolveAssetUrlAsync(AssetCategory assetCategory, string assetPath) {
var firstAttempt = await CachedResolveAsync(assetCategory, assetPath, AssetSource);
return firstAttempt ?? await CachedResolveAsync(assetCategory, assetPath, FallbackAssetSource);
}

private static async Task<string?> CachedResolveAsync(
AssetCategory assetCategory,
string assetPath,
string assetSource
) {
return await ResolveAssetAsync(
assetCategory, assetPath, assetSource,
async (localAssetPath, remoteAssetUrl) => {
if (await LocalAssetService.Instance.HasAssetChangedAsync(localAssetPath, remoteAssetUrl)) {
return await DownloadAndGetAssetUrlAsync(localAssetPath, remoteAssetUrl);
}

return File.Exists(localAssetPath) ? localAssetPath : null;
});
}

private static async Task<string> DownloadAndGetAssetUrlAsync(string localAssetPath, string remoteAssetUrl) {
return await ToolBox.RunSafelyWithResultAsync(async () => {
LocalStorageService.CreateDirectories(localAssetPath);

using var client = new HttpClient();
var stream = await client.GetStreamAsync(remoteAssetUrl);
using var destinationFile = File.OpenWrite(localAssetPath);
await stream.CopyToAsync(destinationFile);

return localAssetPath;
}, exception => {
Console.Out.WriteLine("Oh shit cannot download asset! " + exception.Message);
return null;
});
}

private static async Task<string?> ResolveAssetAsync(
AssetCategory assetCategory,
string assetPath,
string assetSource,
Func<string, string, Task<string?>> resolveAsset) {
var localAssetPath = ConstructLocalAssetPath(assetCategory, assetPath);
var remoteAssetPath = ConstructRemoteAssetUrl(assetCategory, assetPath, assetSource);
return await resolveAsset(localAssetPath, remoteAssetPath);
}

private static string ConstructLocalAssetPath(AssetCategory assetCategory, string assetPath) {
var localAssetPath = Path.Combine(
LocalStorageService.Instance.GetAssetDirectory(),
AssetCategoryName(assetCategory),
CleanAssetPath(assetPath)
);
return localAssetPath;
}

private static string CleanAssetPath(string assetPath) {
var cleanAssetPath = assetPath.Replace('/', Path.DirectorySeparatorChar);
return cleanAssetPath.StartsWith(Path.DirectorySeparatorChar.ToString())
? cleanAssetPath.Substring(1)
: cleanAssetPath;
}

private static string AssetCategoryName(AssetCategory assetCategory) {
return assetCategory.ToString().ToLower();
}

using Microsoft.VisualStudio.Shell;

private static string ConstructRemoteAssetUrl(
AssetCategory assetCategory,
string assetPath,
string assetSource
) {
return assetCategory == AssetCategory.Stickers
? $"{assetSource}/{AssetCategoryName(assetCategory)}/jetbrains/v2{assetPath}"
: $"{assetSource}/{AssetCategoryName(assetCategory)}/{assetPath}";
}
using Task = System.Threading.Tasks.Task;

public static bool CanResolveSync(AssetCategory assetCategory, string assetPath) {
return File.Exists(ConstructLocalAssetPath(assetCategory, assetPath));
namespace doki_theme_visualstudio
{
public enum AssetCategory
{
Stickers,
Backgrounds,
Promotion,
Misc
}

public static string ResolveAssetUrl(AssetCategory category, string assetPath) {
VsTaskLibraryHelper.FileAndForget(Task.Run(async () => {
await ResolveAssetUrlAsync(category, assetPath); // makes sure asset is upto date
}), "dokiTheme/sync/background-update");
return ConstructLocalAssetPath(category, assetPath);
public static class AssetManager
{
public const string AssetSource = "https://doki.assets.unthrottled.io";
public const string FallbackAssetSource = "https://raw.githubusercontent.com/doki-theme/doki-theme-assets/master";

public static async Task<string?> ResolveAssetUrlAsync(AssetCategory assetCategory, string assetPath)
{
var firstAttempt = await CachedResolveAsync(assetCategory, assetPath, AssetSource);
return firstAttempt ?? await CachedResolveAsync(assetCategory, assetPath, FallbackAssetSource);
}

private static async Task<string?> CachedResolveAsync(
AssetCategory assetCategory,
string assetPath,
string assetSource
)
{
return await ResolveAssetAsync(
assetCategory, assetPath, assetSource,
async (localAssetPath, remoteAssetUrl) =>
{
if (await LocalAssetService.Instance.HasAssetChangedAsync(localAssetPath, remoteAssetUrl))
{
return await DownloadAndGetAssetUrlAsync(localAssetPath, remoteAssetUrl);
}

return File.Exists(localAssetPath) ? localAssetPath : null;
});
}

private static async Task<string> DownloadAndGetAssetUrlAsync(string localAssetPath, string remoteAssetUrl)
{
return await ToolBox.RunSafelyWithResultAsync(async () =>
{
LocalStorageService.CreateDirectories(localAssetPath);

using var client = new HttpClient();
var stream = await client.GetStreamAsync(remoteAssetUrl);
using var destinationFile = File.OpenWrite(localAssetPath);
await stream.CopyToAsync(destinationFile);

return localAssetPath;
}, exception =>
{
Console.Out.WriteLine("Oh shit cannot download asset! " + exception.Message);
return null;
});
}

private static async Task<string?> ResolveAssetAsync(
AssetCategory assetCategory,
string assetPath,
string assetSource,
Func<string, string, Task<string?>> resolveAsset)
{
var localAssetPath = ConstructLocalAssetPath(assetCategory, assetPath);
var remoteAssetPath = ConstructRemoteAssetUrl(assetCategory, assetPath, assetSource);
return await resolveAsset(localAssetPath, remoteAssetPath);
}

private static string ConstructLocalAssetPath(AssetCategory assetCategory, string assetPath)
{
var localAssetPath = Path.Combine(
LocalStorageService.Instance.GetAssetDirectory(),
AssetCategoryName(assetCategory),
CleanAssetPath(assetPath)
);
return localAssetPath;
}

private static string CleanAssetPath(string assetPath)
{
var cleanAssetPath = assetPath.Replace('/', Path.DirectorySeparatorChar);
return cleanAssetPath.StartsWith(Path.DirectorySeparatorChar.ToString())
? cleanAssetPath.Substring(1)
: cleanAssetPath;
}

private static string AssetCategoryName(AssetCategory assetCategory)
{
return assetCategory.ToString().ToLower();
}


private static string ConstructRemoteAssetUrl(
AssetCategory assetCategory,
string assetPath,
string assetSource
)
{
return assetCategory == AssetCategory.Stickers
? $"{assetSource}/{AssetCategoryName(assetCategory)}/jetbrains/v2{assetPath}"
: $"{assetSource}/{AssetCategoryName(assetCategory)}/{assetPath}";
}

public static bool CanResolveSync(AssetCategory assetCategory, string assetPath)
{
return File.Exists(ConstructLocalAssetPath(assetCategory, assetPath));
}

public static string ResolveAssetUrl(AssetCategory category, string assetPath)
{
VsTaskLibraryHelper.FileAndForget(Task.Run(async () =>
{
await ResolveAssetUrlAsync(category, assetPath); // makes sure asset is upto date
}), "dokiTheme/sync/background-update");
return ConstructLocalAssetPath(category, assetPath);
}
}
}
}
27 changes: 21 additions & 6 deletions doki-theme-visualstudio/doki-theme-visualstudio.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -353,11 +353,28 @@
<Reference Include="WindowsBase"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.0.31902.203 " ExcludeAssets="runtime"/>
<PackageReference Include="MessagePack">
<Version>2.5.187</Version>
</PackageReference>
<PackageReference Include="Microsoft.IO.Redist">
<Version>6.0.1</Version>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.11.40262" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.VsixColorCompiler">
<Version>17.0.31902.203 </Version>
<Version>17.11.35325.10</Version>
</PackageReference>
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.12.2069">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="System.Net.Http">
<Version>4.3.4</Version>
</PackageReference>
<PackageReference Include="System.Text.Json">
<Version>8.0.5</Version>
</PackageReference>
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.1.1014-preview2"/>
</ItemGroup>
<ItemGroup>
<Content Include="doki_logo.png">
Expand All @@ -378,7 +395,5 @@
<ResourceName>Menus.ctmenu</ResourceName>
</VSCTCompile>
</ItemGroup>
<ItemGroup>
<Folder Include="Themes\"/>
</ItemGroup>
<ItemGroup/>
</Project>
6 changes: 6 additions & 0 deletions doki-theme-visualstudio/source.extension.vsixmanifest
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
<InstallationTarget Version="[17.0,18.0)" Id="Microsoft.VisualStudio.Pro">
<ProductArchitecture>amd64</ProductArchitecture>
</InstallationTarget>
<InstallationTarget Version="[17.0,18.0)" Id="Microsoft.VisualStudio.Community">
<ProductArchitecture>arm64</ProductArchitecture>
</InstallationTarget>
<InstallationTarget Version="[17.0,18.0)" Id="Microsoft.VisualStudio.Pro">
<ProductArchitecture>arm64</ProductArchitecture>
</InstallationTarget>
</Installation>
<Dependencies>
<Dependency Id="Microsoft.Framework.NDP" DisplayName="Microsoft .NET Framework" d:Source="Manual" Version="[4.5,)" />
Expand Down