Skip to content

Commit

Permalink
0.2.0-dev2
Browse files Browse the repository at this point in the history
- Restructured code
- Removed hard-coded categories
- Implemented the `GenerateCategories` function, which generates a dictionary with a pair of category name and value
- Various improvements
  • Loading branch information
nixxoq committed Jul 14, 2024
1 parent f5e350c commit d5590a0
Show file tree
Hide file tree
Showing 14 changed files with 662 additions and 601 deletions.
22 changes: 11 additions & 11 deletions Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@

namespace xp_apps
{
internal static class Program
public static class Program
{
private static void Main()
public static void Main()
{
SimpleLogger.SetupLog();

#if DEBUG
SimpleLogger.Logger.Debug(
$"Current architecture: {Constants.OsArchitecture} | Current OS: {Environment.OSVersion}");
var args = Functions.GetCommandArgs()?.Length > 0
? string.Join(" ", Functions.GetCommandArgs())
$"Current architecture: {Helper.OsArchitecture} | Current OS: {Environment.OSVersion}");
var args = MainScreen.GetCommandArgs()?.Length > 0
? string.Join(" ", MainScreen.GetCommandArgs())
: "No additional arguments";
SimpleLogger.Logger.Debug($"Used command-line arguments: {args}");
#endif

ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

if (Convert.ToBoolean(Updater.CheckForUpdates()))
{
Console.WriteLine(
Expand All @@ -38,7 +42,7 @@ private static void Main()

// Checks if .NET Framework 4.5 or newer is installed
// Its need for TLS 1.2 protocol
if (!Functions.IsDotNet45OrNewer())
if (!MainScreen.IsDotNet45OrNewer())
{
Console.WriteLine(
"This program works only with installed .NET Framework 4.0 and 4.5+\nMake sure you have installed the One-Core-API before installing .NET Framework 4.5+!");
Expand All @@ -47,11 +51,7 @@ private static void Main()
return;
}

// Initializing Security Protocols for HTTPS requests
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

Functions.ParseArgs();
MainScreen.ParseArgs();
}
}
}
2 changes: 1 addition & 1 deletion Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,4 @@
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0-dev2")]
3 changes: 1 addition & 2 deletions Updater/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
using System.Net;
using System.Threading;
using Ionic.Zip;
using xp_apps.sources;

namespace xp_apps.Updater
{
Expand All @@ -15,7 +14,7 @@ private static void Main(string[] args)

using (var client = new WebClient())
{
client.DownloadFile(Constants.LatestReleaseZip, "xp-apps.zip");
client.DownloadFile(sources.Updater.LatestReleaseZip, "xp-apps.zip");
}

using (var zipFile = new ZipFile("xp-apps.zip"))
Expand Down
259 changes: 259 additions & 0 deletions sources/Applications.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using Newtonsoft.Json;
using xp_apps.sources.Structures;
using static xp_apps.sources.Structures.ApplicationStructure;

namespace xp_apps.sources
{
public abstract class Applications
{
private const string ApplicationDb = "https://raw.githubusercontent.com/nixxoq/xp-apps/development/upd.json";
private static string ApplicationsListName => Helper.ExtractFileNameFromUrl(ApplicationDb);

private static string ApplicationsListPath => Path.Combine(
Helper.WorkDir,
Cache.CacheFolder,
ApplicationsListName);

private static readonly bool IsAppsListExist = File.Exists(ApplicationsListPath);

public static readonly ApplicationStructure.Applications ProgramsList = GetUpdates();

/// <summary>
/// Creates the application list file if it doesn't exist on cache folder.
/// </summary>
private static string UpdateApplicationList(bool isNeedUpdate = false)
{
Helper.CreateApplicationFolder();
if (IsAppsListExist && !isNeedUpdate)
return File.ReadAllText(ApplicationsListPath);

Helper.DownloadFile(ApplicationDb, ApplicationsListPath);
Console.Clear();

return File.ReadAllText(ApplicationsListPath);
}

private static bool IsNeedUpdate()
{
// true - need update; false - up to date or not exist

var client = Helper.GetClient();
client.OpenRead(ApplicationDb);
var filesize = Convert.ToInt64(client.ResponseHeaders.Get("Content-Length"));

if (!File.Exists(ApplicationsListPath) || new FileInfo(ApplicationsListPath).Length != filesize)
return true;

#if DEBUG
SimpleLogger.Logger.Debug("main application list is up-to-date.");
#endif
return false;
}

/// <summary>
/// Get all applications available to install
/// </summary>
/// <param name="categories">Dictionary of application categories and their program containers</param>
public static void GetApplications(Dictionary<string, List<ProgramContainer>> categories)
{
Console.WriteLine("List of available applications:\nFormat:\n[Category]\n [Application Name]\n");

foreach (var category in categories)
{
Console.WriteLine($"{category.Key}:");
foreach (var (programName, programDetails) in GetProgramDetails(category.Value))
{
Console.WriteLine(programDetails.Aliases.Any()
? $" {programName} | aliases: {string.Join(", ", programDetails.Aliases)}"
: $" {programName}");
}
}
}

/// <summary>
/// Finds the application details based on the provided application name.
/// </summary>
/// <param name="appName">The name of the application to find.</param>
/// <returns>The details of the application if found, otherwise null.</returns>
private static ProgramDetails FindApplication(string appName)
{
return Categories
.Select(
category =>
FindApplicationInCategory(appName, category.Value, category.Key)
)
.FirstOrDefault(result => result != null);
}

/// <summary>
/// Finds the application details in a specific category.
/// </summary>
/// <param name="appName">The name of the application to find.</param>
/// <param name="category">The category to search in.</param>
/// <param name="categoryName">The name of the category for debug output.</param>
/// <returns>The details of the application if found, otherwise null.</returns>
private static ProgramDetails FindApplicationInCategory(string appName, List<ProgramContainer> category,
string categoryName)
{
#if DEBUG
SimpleLogger.Logger.Debug($"Searching {appName} in {categoryName} category...");
#endif

foreach (var (programName, programDetails) in GetProgramDetails(category))
{
if (programName.Equals(appName, StringComparison.OrdinalIgnoreCase) &&
(programDetails.Architecture.Equals("any", StringComparison.OrdinalIgnoreCase) ||
programDetails.Architecture.Equals(Helper.OsArchitecture, StringComparison.OrdinalIgnoreCase)))
return programDetails;

if (programDetails.Aliases.Any(
alias => alias.Equals(appName, StringComparison.OrdinalIgnoreCase) &&
(programDetails.Architecture.Equals("any", StringComparison.OrdinalIgnoreCase) ||
programDetails.Architecture.Equals(Helper.OsArchitecture,
StringComparison.OrdinalIgnoreCase))
))
return programDetails;
}

return null;
}

/// <summary>
/// Downloads a file from the specified URL and saves it with the given filename.
/// Displays a progress animation in the console while downloading.
/// </summary>
/// <param name="appName">Application name to install</param>
/// <param name="isForce">Force download if file already exists</param>
public static void InstallApplication(string appName, bool isForce)
{
var applicationDetails = FindApplication(appName);
if (applicationDetails == null)
{
Console.Write($"Could not find application {appName}.");

// check if similar applications exist
var similarApps = FindSimilarApplications(appName);
if (similarApps.Any())
Console.WriteLine($" Did you mean: {string.Join(", ", similarApps.Distinct())}?");

return;
}

var filename = applicationDetails.Filename;
var url = applicationDetails.Url;

Console.WriteLine($"Found application {appName}");

var client = Helper.GetClient();
client.OpenRead(url ?? string.Empty);
var filesize = Convert.ToInt64(client.ResponseHeaders.Get("Content-Length"));

var downloadedIn = Path.Combine(Helper.WorkDir, filename);
Console.WriteLine(
$"Application name: {applicationDetails.Name}\nSize (in MB): {filesize / (1024 * 1024)}" +
$"Filename: {applicationDetails.Filename}\nWill be downloaded in {downloadedIn}");

if (File.Exists(filename) && isForce)
File.Delete(filename);

// check if downloaded file is not corrupted
if (filename != null && File.Exists(filename) && new FileInfo(filename).Length == filesize)
{
Console.WriteLine(
"File already exists and is not corrupted. Skipping download." +
$"\nIf you want force download, use 'xp-apps.exe -i {appName} --force'"
);
return;
}

Helper.DownloadFile(url, downloadedIn);
}

/// <summary>
/// Finds similar applications based on the provided application name.
/// </summary>
/// <param name="appName">The name of the application to find.</param>
/// <returns>The list of similar applications if found, otherwise null.</returns>
private static List<string> FindSimilarApplications(string appName)
{
var allApps = Categories
.SelectMany(c => GetProgramDetails(c.Value))
.SelectMany(p => new[] { p.ProgramName }.Concat(p.ProgramDetails.Aliases))
.ToList();

var threshold = Math.Max(appName.Length / 2, 2);

return allApps
.Where(
name => LevenshteinDistance(appName, name) <= threshold ||
name.IndexOf(appName, StringComparison.OrdinalIgnoreCase) >= 0
)
.Distinct()
.OrderBy(name => LevenshteinDistance(appName, name))
.ThenBy(name => name)
.Take(5)
.ToList();
}

/// <summary>
/// Calculates the Levenshtein distance between two strings.
/// </summary>
private static int LevenshteinDistance(string s, string t)
{
if (string.IsNullOrEmpty(s)) return t?.Length ?? 0;
if (string.IsNullOrEmpty(t)) return s.Length;

int n = s.Length, m = t.Length;

var d = new int[n + 1, m + 1];

for (var i = 0; i <= n; i++) d[i, 0] = i;
for (var j = 0; j <= m; j++) d[0, j] = j;

for (var i = 1; i <= n; i++)
for (var j = 1; j <= m; j++)
d[i, j] = Math.Min(
Math.Min(d[i - 1, j] + 1, d[i, j - 1] + 1),
d[i - 1, j - 1] + (s[i - 1] == t[j - 1] ? 0 : 1)
);

return d[n, m];
}

/// <summary>
/// Retrieves the applications list updates from the server and deserializes them into an Applications object.
/// </summary>
/// <returns>The deserialized Applications object containing the updates.</returns>
private static ApplicationStructure.Applications GetUpdates()
{
if (!Helper.IsNetworkAvailable())
{
if (!IsAppsListExist)
{
Console.WriteLine("No internet connection. Please check your internet connection and try again.");
Environment.Exit(0);
}
else
{
Console.WriteLine("No internet connection. Using cached application list.");
return JsonConvert.DeserializeObject<ApplicationStructure.Applications>(
File.ReadAllText(ApplicationsListPath));
}
}

ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072;

var jsonContent = IsNeedUpdate()
? UpdateApplicationList(true)
: File.ReadAllText(ApplicationsListPath);

return JsonConvert.DeserializeObject<ApplicationStructure.Applications>(jsonContent);
}
}
}
Loading

0 comments on commit d5590a0

Please sign in to comment.