Added primitive search mechanism in backend.
Began implementing search mechanism for frontend.
This commit is contained in:
18
Props/Services/Modules/IMetricsManager.cs
Normal file
18
Props/Services/Modules/IMetricsManager.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Props.Models.Search;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public interface IMetricsManager
|
||||
{
|
||||
public IEnumerable<ProductListingInfo> RetrieveTopListings(int max = 10);
|
||||
|
||||
public IEnumerable<string> RetrieveCommonKeywords(int max = 50);
|
||||
|
||||
public void RegisterSearchQuery(string query);
|
||||
|
||||
public void RegisterListing(ProductListing productListing, string shopName);
|
||||
}
|
||||
}
|
12
Props/Services/Modules/ISearchManager.cs
Normal file
12
Props/Services/Modules/ISearchManager.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Props.Models.Search;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public interface ISearchManager
|
||||
{
|
||||
public IEnumerable<ProductListing> Search(string query, SearchOutline searchOutline);
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,9 +7,10 @@ using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public interface IShopManager
|
||||
public interface IShopManager : IDisposable
|
||||
{
|
||||
public IEnumerable<string> AvailableShops();
|
||||
public Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline);
|
||||
public IEnumerable<string> GetAllShopNames();
|
||||
public IShop GetShop(string name);
|
||||
public IEnumerable<IShop> GetAllShops();
|
||||
}
|
||||
}
|
85
Props/Services/Modules/LiveMetricsManager.cs
Normal file
85
Props/Services/Modules/LiveMetricsManager.cs
Normal file
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Props.Data;
|
||||
using Props.Models.Search;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public class LiveMetricsManager : IMetricsManager
|
||||
{
|
||||
private ILogger<LiveMetricsManager> logger;
|
||||
ApplicationDbContext dbContext;
|
||||
public LiveMetricsManager(ApplicationDbContext dbContext, ILogger<LiveMetricsManager> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.dbContext = dbContext;
|
||||
}
|
||||
public IEnumerable<ProductListingInfo> RetrieveTopListings(int max)
|
||||
{
|
||||
if (dbContext.ProductListingInfos == null) return null;
|
||||
return (from l in dbContext.ProductListingInfos
|
||||
orderby l.Hits descending
|
||||
select l).Take(max);
|
||||
}
|
||||
|
||||
public IEnumerable<string> RetrieveCommonKeywords(int max)
|
||||
{
|
||||
if (dbContext.Keywords == null) return null;
|
||||
return (from k in dbContext.Keywords
|
||||
orderby k.Hits descending
|
||||
select k.Word).Take(max);
|
||||
}
|
||||
|
||||
public void RegisterSearchQuery(string query)
|
||||
{
|
||||
query = query.ToLower();
|
||||
string[] tokens = query.Split(' ');
|
||||
QueryWordInfo[] wordInfos = new QueryWordInfo[tokens.Length];
|
||||
for (int wordIndex = 0; wordIndex < tokens.Length; wordIndex++)
|
||||
{
|
||||
QueryWordInfo queryWordInfo = dbContext.Keywords.Where((k) => k.Word.ToLower().Equals(tokens[wordIndex])).SingleOrDefault() ?? new QueryWordInfo();
|
||||
if (queryWordInfo.Hits == 0)
|
||||
{
|
||||
queryWordInfo.Word = tokens[wordIndex];
|
||||
dbContext.Keywords.Add(queryWordInfo);
|
||||
}
|
||||
queryWordInfo.Hits += 1;
|
||||
wordInfos[wordIndex] = queryWordInfo;
|
||||
}
|
||||
for (int wordIndex = 0; wordIndex < tokens.Length; wordIndex++)
|
||||
{
|
||||
for (int beforeIndex = 0; beforeIndex < wordIndex; beforeIndex++)
|
||||
{
|
||||
wordInfos[wordIndex].Preceding.Add(wordInfos[beforeIndex]);
|
||||
}
|
||||
for (int afterIndex = wordIndex; afterIndex < tokens.Length; afterIndex++)
|
||||
{
|
||||
wordInfos[wordIndex].Following.Add(wordInfos[afterIndex]);
|
||||
}
|
||||
}
|
||||
dbContext.SaveChanges();
|
||||
|
||||
}
|
||||
|
||||
public void RegisterListing(ProductListing productListing, string shopName)
|
||||
{
|
||||
ProductListingInfo productListingInfo =
|
||||
(from info in dbContext.ProductListingInfos
|
||||
where info.ProductUrl.Equals(productListing.URL)
|
||||
select info).SingleOrDefault() ?? new ProductListingInfo();
|
||||
if (productListingInfo.Hits == 0)
|
||||
{
|
||||
dbContext.Add(productListingInfo);
|
||||
}
|
||||
productListingInfo.ShopName = shopName;
|
||||
productListingInfo.ProductName = productListing.Name;
|
||||
productListingInfo.ProductUrl = productListing.URL;
|
||||
productListingInfo.LastUpdated = DateTime.UtcNow;
|
||||
productListingInfo.Hits += 1;
|
||||
dbContext.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
58
Props/Services/Modules/LiveSearchManager.cs
Normal file
58
Props/Services/Modules/LiveSearchManager.cs
Normal file
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Castle.Core.Logging;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Props.Models.Search;
|
||||
using Props.Options;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public class LiveSearchManager : ISearchManager
|
||||
{
|
||||
private ILogger<LiveSearchManager> logger;
|
||||
private SearchOptions searchOptions;
|
||||
private IShopManager shopManager;
|
||||
private IMetricsManager metricsManager;
|
||||
|
||||
public LiveSearchManager(IMetricsManager metricsManager, IShopManager shopManager, IConfiguration configuration, ILogger<LiveSearchManager> logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.metricsManager = metricsManager;
|
||||
this.shopManager = shopManager;
|
||||
this.searchOptions = configuration.GetSection(SearchOptions.Search).Get<SearchOptions>();
|
||||
}
|
||||
public IEnumerable<ProductListing> Search(string query, SearchOutline searchOutline)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(query)) throw new ArgumentException($"Query \"{query}\" is null or whitepsace.");
|
||||
if (searchOutline == null) throw new ArgumentNullException("searchOutline");
|
||||
List<ProductListing> results = new List<ProductListing>();
|
||||
metricsManager.RegisterSearchQuery(query);
|
||||
logger.LogDebug("Searching for \"{0}\".", query);
|
||||
|
||||
foreach (string shopName in shopManager.GetAllShopNames())
|
||||
{
|
||||
if (searchOutline.Enabled[shopName])
|
||||
{
|
||||
logger.LogDebug("Checking \"{0}\".", shopName);
|
||||
int amount = 0;
|
||||
foreach (ProductListing product in shopManager.GetShop(shopName).Search(query, searchOutline.Filters))
|
||||
{
|
||||
if (searchOutline.Filters.Validate(product))
|
||||
{
|
||||
amount += 1;
|
||||
metricsManager.RegisterListing(product, shopName);
|
||||
results.Add(product);
|
||||
}
|
||||
if (amount >= searchOptions.MaxResults) break;
|
||||
}
|
||||
logger.LogDebug("Found {0} listings that satisfy the search filters from {1}.", amount, shopName);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Props.Data;
|
||||
using Props.Models.Search;
|
||||
using Props.Options;
|
||||
using Props.Shop.Framework;
|
||||
|
||||
namespace Props.Services.Modules
|
||||
{
|
||||
public class LocalShopManager : IShopManager
|
||||
public class ModularShopManager : IShopManager
|
||||
{
|
||||
private ILogger<LocalShopManager> logger;
|
||||
private ILogger<ModularShopManager> logger;
|
||||
private Dictionary<string, IShop> shops;
|
||||
private ModulesOptions options;
|
||||
private IConfiguration configuration;
|
||||
public LocalShopManager(IConfiguration configuration, ILogger<LocalShopManager> logger)
|
||||
private bool disposedValue;
|
||||
|
||||
public ModularShopManager(IConfiguration configuration, ILogger<ModularShopManager> logger)
|
||||
{
|
||||
this.configuration = configuration;
|
||||
this.logger = logger;
|
||||
@@ -34,35 +40,25 @@ namespace Props.Services.Modules
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> AvailableShops()
|
||||
public IEnumerable<string> GetAllShopNames()
|
||||
{
|
||||
return shops.Keys;
|
||||
}
|
||||
|
||||
public async Task<IList<ProductListing>> Search(string query, SearchOutline searchOutline)
|
||||
|
||||
public IShop GetShop(string name)
|
||||
{
|
||||
List<ProductListing> results = new List<ProductListing>();
|
||||
foreach (string shopName in shops.Keys)
|
||||
{
|
||||
if (!searchOutline.Disabled[shopName])
|
||||
{
|
||||
int amount = 0;
|
||||
await foreach (ProductListing product in shops[shopName].Search(query, searchOutline.Filters))
|
||||
{
|
||||
if (searchOutline.Filters.Validate(product))
|
||||
{
|
||||
amount += 1;
|
||||
results.Add(product);
|
||||
}
|
||||
if (amount >= options.MaxResults) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return results;
|
||||
return shops[name];
|
||||
}
|
||||
|
||||
public IEnumerable<IShop> GetAllShops()
|
||||
{
|
||||
return shops.Values;
|
||||
}
|
||||
|
||||
private IEnumerable<IShop> LoadShops(string shopsDir, string shopRegex, bool recursiveLoad)
|
||||
{
|
||||
Stack<Task> asyncInitTasks = new Stack<Task>();
|
||||
Stack<string> directories = new Stack<string>();
|
||||
directories.Push(shopsDir);
|
||||
string currentDirectory = null;
|
||||
@@ -90,7 +86,11 @@ namespace Props.Services.Modules
|
||||
IShop shop = Activator.CreateInstance(type) as IShop;
|
||||
if (shop != null)
|
||||
{
|
||||
// TODO: load persisted shop data.
|
||||
shop.Initialize(null);
|
||||
asyncInitTasks.Push(shop.InitializeAsync(null));
|
||||
success += 1;
|
||||
logger.LogDebug("Loaded \"{0}\".", shop.ShopName);
|
||||
yield return shop;
|
||||
}
|
||||
}
|
||||
@@ -102,6 +102,32 @@ namespace Props.Services.Modules
|
||||
}
|
||||
}
|
||||
}
|
||||
logger.LogDebug("Waiting for all shops to finish asynchronous initialization.");
|
||||
Task.WaitAll(asyncInitTasks.ToArray());
|
||||
logger.LogDebug("All shops finished asynchronous initialization.");
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (string shopName in shops.Keys)
|
||||
{
|
||||
// TODO: Get shop data to persist.
|
||||
shops[shopName].Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(disposing: true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user