Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.
Open
212 changes: 106 additions & 106 deletions ServerlessLibraryAPI/CacheService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,127 +9,127 @@

namespace ServerlessLibrary
{
public interface ICacheService
public interface ICacheService
{
IList<LibraryItemWithStats> GetCachedItems();
}

//https://stackoverflow.com/questions/44723017/in-memory-caching-with-auto-regeneration-on-asp-net-core
public class CacheService : ICacheService
{
protected readonly IMemoryCache _cache;
private readonly ILibraryStore libraryStore;
private readonly ILogger logger;

private Task LoadingTask = Task.CompletedTask;
private Timer Timer = null;
private bool LoadingBusy = false;
private bool isCacheLoadedOnce = false;

public CacheService(IMemoryCache cache, ILibraryStore libraryStore, ILogger<CacheService> logger)
{
IList<LibraryItemWithStats> GetCachedItems();
this._cache = cache;
this.libraryStore = libraryStore;
this.logger = logger;
InitTimer();
}

//https://stackoverflow.com/questions/44723017/in-memory-caching-with-auto-regeneration-on-asp-net-core
public class CacheService : ICacheService
private void InitTimer()
{
protected readonly IMemoryCache _cache;
private readonly ILibraryStore libraryStore;
private readonly ILogger logger;
_cache.Set<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = new List<LibraryItemWithStats>(), IsBusy = true });

private Task LoadingTask = Task.CompletedTask;
private Timer Timer = null;
private bool LoadingBusy = false;
private bool isCacheLoadedOnce = false;

public CacheService(IMemoryCache cache, ILibraryStore libraryStore, ILogger<CacheService> logger)
{
this._cache = cache;
this.libraryStore = libraryStore;
this.logger = logger;
InitTimer();
}
Timer = new Timer(TimerTickAsync, null, 1000, ServerlessLibrarySettings.SLCacheRefreshIntervalInSeconds * 1000);
}

private void InitTimer()
public IList<LibraryItemWithStats> GetCachedItems()
{
// Make a blocking call to load cache on first time call.
if (!isCacheLoadedOnce)
{
try
{
_cache.Set<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = new List<LibraryItemWithStats>(), IsBusy = true });

Timer = new Timer(TimerTickAsync, null, 1000, ServerlessLibrarySettings.SLCacheRefreshIntervalInSeconds * 1000);
logger.LogInformation("Loading initial cache");
IList<LibraryItemWithStats> items = this.ConstructCache().Result;
_cache.Set(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = items, IsBusy = false });
logger.LogInformation("Loaded {0} items into cache", items.Count());
}

public IList<LibraryItemWithStats> GetCachedItems()
catch (Exception ex)
{
// Make a blocking call to load cache on first time call.
if (!isCacheLoadedOnce)
{
try
{
logger.LogInformation("Loading initial cache");
IList<LibraryItemWithStats> items = this.ConstructCache().Result;
_cache.Set(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = items, IsBusy = false });
logger.LogInformation("Loaded {0} items into cache", items.Count());
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to load cache in first call");
}
}

logger.LogInformation("Successfully loaded initial cache");
isCacheLoadedOnce = true;
return _cache.Get<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY).Result;
this.logger.LogError(ex, "Failed to load cache in first call");
}
}

private async void TimerTickAsync(object state)
{
logger.LogInformation("Cache refresh timer fired");
if (!isCacheLoadedOnce || LoadingBusy)
{
logger.LogWarning("Skipping cache refresh");
return;
}
logger.LogInformation("Successfully loaded initial cache");
isCacheLoadedOnce = true;
return _cache.Get<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY).Result;
}

try
{
LoadingBusy = true;
LoadingTask = LoadCaches();
await LoadingTask;
}
catch
{
// do not crash the app
}
finally
{
LoadingBusy = false;
}
}
private async Task LoadCaches()
{
try
{
logger.LogInformation("Starting cache refresh");
var items = await ConstructCache();
_cache.Set<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = items, IsBusy = false });
logger.LogInformation("Updated cache with {0} items", items.Count());
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to load cache");
}
}
private async Task<IList<LibraryItemWithStats>> ConstructCache()
{
logger.LogInformation("Starting ConstructCache");
IList<LibraryItem> libraryItems;
IList<LibraryItemWithStats> libraryItemsWithStats = new List<LibraryItemWithStats>();
libraryItems = await this.libraryStore.GetAllItems();
logger.LogInformation("Cosmos DB returned {0} results", libraryItems.Count());
var stats = await StorageHelper.getSLItemRecordsAsync();
logger.LogInformation("Storage returned {0} results", stats.Count());
foreach (var storeItem in libraryItems)
{
var item = storeItem.ConvertTo<LibraryItemWithStats>();
var itemStat = stats.Where(s => s.id == storeItem.Id.ToString()).FirstOrDefault();
item.TotalDownloads = itemStat != null && itemStat.totalDownloads > 0 ? itemStat.totalDownloads : 1;
item.Likes = itemStat != null && itemStat.likes > 0 ? itemStat.likes : 0;
item.Dislikes = itemStat != null && itemStat.dislikes > 0 ? itemStat.dislikes : 0;
libraryItemsWithStats.Add(item);
}
private async void TimerTickAsync(object state)
{
logger.LogInformation("Cache refresh timer fired");
if (!isCacheLoadedOnce || LoadingBusy)
{
logger.LogWarning("Skipping cache refresh");
return;
}

logger.LogInformation("ConstructCache returned {0} items", libraryItemsWithStats.Count());
return libraryItemsWithStats;
}
try
{
LoadingBusy = true;
LoadingTask = LoadCaches();
await LoadingTask;
}
catch
{
// do not crash the app
}
finally
{
LoadingBusy = false;
}
}

public class LibraryItemsResult
private async Task LoadCaches()
{
try
{
logger.LogInformation("Starting cache refresh");
var items = await ConstructCache();
_cache.Set<LibraryItemsResult>(ServerlessLibrarySettings.CACHE_ENTRY, new LibraryItemsResult() { Result = items, IsBusy = false });
logger.LogInformation("Updated cache with {0} items", items.Count());
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to load cache");
}
}
private async Task<IList<LibraryItemWithStats>> ConstructCache()
{
public IList<LibraryItemWithStats> Result { get; set; }
public bool IsBusy { get; set; }
logger.LogInformation("Starting ConstructCache");
IList<LibraryItem> libraryItems;
IList<LibraryItemWithStats> libraryItemsWithStats = new List<LibraryItemWithStats>();
libraryItems = await this.libraryStore.GetAllItems();
logger.LogInformation("Cosmos DB returned {0} results", libraryItems.Count());
var stats = await StorageHelper.getSLItemRecordsAsync();
logger.LogInformation("Storage returned {0} results", stats.Count());
foreach (var storeItem in libraryItems)
{
var item = storeItem.ConvertTo<LibraryItemWithStats>();
var itemStat = stats.Where(s => s.id == storeItem.Id.ToString()).FirstOrDefault();
item.TotalDownloads = itemStat != null && itemStat.totalDownloads > 0 ? itemStat.totalDownloads : 1;
item.Likes = itemStat != null && itemStat.likes > 0 ? itemStat.likes : 0;
item.Dislikes = itemStat != null && itemStat.dislikes > 0 ? itemStat.dislikes : 0;
libraryItemsWithStats.Add(item);
}

logger.LogInformation("ConstructCache returned {0} items", libraryItemsWithStats.Count());
return libraryItemsWithStats;
}
}

public class LibraryItemsResult
{
public IList<LibraryItemWithStats> Result { get; set; }
public bool IsBusy { get; set; }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,24 @@ class ActionBar extends Component {
return "vscode://vscode.git/clone?url=" + encodeURIComponent(repository);
}

getOpenInGithubDevLink(repository) {
if(repository){
return repository.replace("github\.com", "github\.dev");
} else {
return repository;
}
}

openInVSCodeClick() {
this.updateDownloadCount(this.props.id);
this.trackUserActionEvent("/sample/openinvscode");
}

openInGithubDevClick() {
this.updateDownloadCount(this.props.id);
this.trackUserActionEvent("/sample/openingithubdev");
}

trackUserActionEvent(eventName) {
let eventData = {
id: this.props.id,
Expand Down Expand Up @@ -76,6 +89,18 @@ class ActionBar extends Component {
</div>
</FabricLink>
</div>
<div className="action-item">
<FabricLink
href={this.getOpenInGithubDevLink(repository)}
disabled={!repository}
onClick={this.openInGithubDevClick}
>
<div className="action-link-wrapper">
<Icon iconName="Edit" className="fabric-icon-link" />
<span className="action-link-text">Edit in GitHub.dev</span>
</div>
</FabricLink>
</div>
<div className="action-item">
<FabricLink
href={this.getDeployLink(template)}
Expand Down Expand Up @@ -107,4 +132,4 @@ class ActionBar extends Component {
}
}

export default ActionBar;
export default ActionBar;
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export const technologies = [
"Functions 3.x",
"Functions 2.x",
"Functions 1.x",
"Logic Apps",
Expand Down
Loading