diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..dfc3d00
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,470 @@
+
+# Created by https://www.gitignore.io/api/node,visualstudio,visualstudiocode
+# Edit at https://www.gitignore.io/?templates=node,visualstudio,visualstudiocode
+
+### Node ###
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# rollup.js default build output
+dist/
+
+# Uncomment the public line if your project uses Gatsby
+# https://nextjs.org/blog/next-9-1#public-directory-support
+# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
+# public
+
+# Storybook build outputs
+.out
+.storybook-out
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# Temporary folders
+tmp/
+temp/
+
+### VisualStudioCode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+
+### VisualStudioCode Patch ###
+# Ignore all local history of files
+.history
+
+### VisualStudio ###
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# End of https://www.gitignore.io/api/node,visualstudio,visualstudiocode
diff --git a/PMF/PMF.csproj b/PMF/PMF.csproj
new file mode 100644
index 0000000..3220f27
--- /dev/null
+++ b/PMF/PMF.csproj
@@ -0,0 +1,14 @@
+
+
+
+ netstandard2.0
+ PMF
+ 0.0.1
+ PMF
+
+
+
+
+
+
+
diff --git a/PMF/src/Config.cs b/PMF/src/Config.cs
new file mode 100644
index 0000000..2bfd20b
--- /dev/null
+++ b/PMF/src/Config.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+
+namespace PMF
+{
+ public static class Config
+ {
+ // This is project manifest, not package manifests, those are handled automagically
+ public static string ManifestFileName { get; set; }
+
+ public static string PackageInstallationFolder { get; set; }
+
+ public static string RepositoryEndpoint { get; set; }
+
+ public static Version CurrentSdkVersion { get; set; }
+
+ public static bool IsDebugging { get; set; }
+
+ public static string TemporaryFolder = ".pmf-temp";
+
+ public static void DEBUG(string message)
+ {
+ Console.WriteLine("DEBUG: " + message);
+ }
+ }
+}
diff --git a/PMF/src/Exceptions/NotFoundException.cs b/PMF/src/Exceptions/NotFoundException.cs
new file mode 100644
index 0000000..c9bbb0d
--- /dev/null
+++ b/PMF/src/Exceptions/NotFoundException.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF.Exceptions
+{
+ ///
+ /// Happens when iterating through an array and an object matching it's description is not found
+ ///
+ public class NotFoundException : Exception
+ {
+ }
+}
diff --git a/PMF/src/Extensions.cs b/PMF/src/Extensions.cs
new file mode 100644
index 0000000..a1e2510
--- /dev/null
+++ b/PMF/src/Extensions.cs
@@ -0,0 +1,48 @@
+using PMF.Exceptions;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public static class Extensions
+ {
+ ///
+ /// Nice hack to reuse this bit of code very effectively, this method is just used in List where T is Package, otherwise this method doesn't even show up
+ ///
+ ///
+ ///
+ public static bool Remove(this List list, string id)
+ {
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (list[i].ID == id)
+ {
+ list.RemoveAt(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+
+ ///
+ /// Same as Remove, but it Retrieves
+ ///
+ ///
+ ///
+ ///
+ public static Package GetPackage(this List list, string id)
+ {
+ if (id == null || id.Length == 0)
+ throw new ArgumentNullException();
+
+ Package package = list.Find((p) => p.ID == id);
+ if (package == null)
+ throw new NotFoundException();
+
+ return package;
+ }
+ }
+}
diff --git a/PMF/src/GlobalSuppressions.cs b/PMF/src/GlobalSuppressions.cs
new file mode 100644
index 0000000..fa57d7f
--- /dev/null
+++ b/PMF/src/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("Style", "IDE1006:Naming Styles")]
diff --git a/PMF/src/Managers/LocalPackageManager.cs b/PMF/src/Managers/LocalPackageManager.cs
new file mode 100644
index 0000000..d157109
--- /dev/null
+++ b/PMF/src/Managers/LocalPackageManager.cs
@@ -0,0 +1,124 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Text;
+
+namespace PMF.Managers
+{
+ ///
+ /// Manages all the local files
+ ///
+ public static class LocalPackageManager
+ {
+ public static List PackageList { get; private set; }
+
+ ///
+ /// Does all the checking locally when the program starts
+ /// THIS NEEDS TO BE CALLED!
+ ///
+ public static void Start()
+ {
+ validateManifestFile();
+
+ try
+ {
+ var json = File.ReadAllText(Config.ManifestFileName);
+ PackageList = JsonConvert.DeserializeObject>(json);
+ }
+ catch (FileNotFoundException)
+ {
+ // Something failed with validateManifestFile()
+ }
+ }
+
+ ///
+ /// Saves everything to disk
+ /// THIS NEEDS TO BE CALLED!
+ ///
+ public static void Stop()
+ {
+ validateManifestFile();
+
+ var json = JsonConvert.SerializeObject(PackageList);
+
+ try
+ {
+ File.WriteAllText(Config.ManifestFileName, json);
+ Directory.Delete(Config.TemporaryFolder, true);
+ }
+ catch (IOException)
+ {
+ // Something failed with validateManifestFile()
+ }
+ }
+
+ private static void validateManifestFile()
+ {
+ if (!File.Exists(Config.ManifestFileName))
+ File.Create(Config.ManifestFileName).Close();
+ if (PackageList == null)
+ PackageList = new List();
+ }
+
+ public static bool IsPackageInstalled(string id, out Package package, out string packageDirectory)
+ {
+ package = null;
+
+ packageDirectory = Path.Combine(Config.PackageInstallationFolder, id);
+ if (!Directory.Exists(packageDirectory))
+ return false;
+
+ try
+ {
+ package = PackageList.GetPackage(id);
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ public static bool RemovePackage(string id)
+ {
+ if (string.IsNullOrEmpty(id))
+ throw new ArgumentNullException();
+
+ try
+ {
+ string packageDirectory = Path.Combine(Config.PackageInstallationFolder, id);
+ Directory.Delete(packageDirectory, true);
+ }
+ catch
+ {
+ // Do nothing, user probably already deleted the folder
+ }
+
+ return PackageList.Remove(id);
+ }
+
+ ///
+ /// Extracts zip files and registeres this package as installed
+ ///
+ /// The package which is to be installed
+ /// The version of the asset being installed
+ ///
+ public static void InstallPackage(Package remotePackage, Asset asset, string zipPath, out Package package)
+ {
+ ZipFile.ExtractToDirectory(Path.Combine(zipPath, asset.FileName), Path.Combine(Config.PackageInstallationFolder, remotePackage.ID));
+
+ foreach (var dependency in asset.Dependencies)
+ ZipFile.ExtractToDirectory(Path.Combine(zipPath, dependency.FileName), Path.Combine(Config.PackageInstallationFolder, remotePackage.ID, "Dependencies", dependency.FileName));
+
+ remotePackage.Assets.Clear();
+ remotePackage.Assets.Add(asset);
+
+ package = remotePackage;
+
+ PackageList.Add(remotePackage);
+ }
+ }
+}
diff --git a/PMF/src/Managers/PackageManager.cs b/PMF/src/Managers/PackageManager.cs
new file mode 100644
index 0000000..537c3a6
--- /dev/null
+++ b/PMF/src/Managers/PackageManager.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Compression;
+using System.Text;
+
+namespace PMF.Managers
+{
+ public static class PackageManager
+ {
+ ///
+ /// Installs a package given a version
+ ///
+ /// The id of the package
+ /// The version of the asset
+ /// true Installation successful, false already installed
+ public static PackageState InstallPackage(Package package, Asset asset)
+ {
+ // If it is not installed, packageDirectory will have the value of the directory where the package should be
+ string zipFile = RemotePackageManager.DownloadAsset(package.ID, asset);
+ LocalPackageManager.InstallPackage(package, asset, zipFile, out package);
+ return PackageState.Installed;
+ }
+
+ ///
+ /// Installs a package given a version
+ ///
+ /// The id of the package
+ /// The version of the asset
+ /// true Installation successful, false already installed
+ public static PackageState Install(string id, Version version, out Package package)
+ {
+ package = null;
+
+ // check if is already installed
+ if (!LocalPackageManager.IsPackageInstalled(id, out Package localPackage, out string packageDirectory))
+ {
+ // get package info for version
+ Package remotePackage = RemotePackageManager.GetPackageInfo(id);
+
+ if (remotePackage == null)
+ return PackageState.NotExisting;
+
+ Asset asset = remotePackage.GetAssetVersion(version);
+
+ if (asset == null)
+ return PackageState.VersionNotFound;
+
+ // If it is not installed, packageDirectory will have the value of the directory where the package should be
+ package = remotePackage;
+ return InstallPackage(remotePackage, asset);
+ }
+ else
+ {
+ return PackageState.AlreadyInstalled;
+ }
+ }
+
+ ///
+ /// Installs a package to the most recent version given an sdk version
+ ///
+ ///
+ /// true update succes, false update failed or cancelled
+ public static PackageState InstallBySdkVersion(string id, out Package package)
+ {
+ package = null;
+
+ // check if is already installed
+ if (LocalPackageManager.IsPackageInstalled(id, out Package localPackage, out string packageDirectory))
+ return PackageState.AlreadyInstalled;
+
+ Package remotePackage = RemotePackageManager.GetPackageInfo(id);
+
+ if (remotePackage == null)
+ return PackageState.NotExisting;
+
+ Asset asset = RemotePackageManager.GetAssetLatestVersionBySdkVersion(remotePackage);
+
+ if (asset == null)
+ return PackageState.VersionNotFound;
+
+ if (validateSdkVersion(asset))
+ {
+ // If it is not installed, packageDirectory will have the value of the directory where the package should be
+ package = remotePackage;
+ return InstallPackage(remotePackage, asset);
+ }
+
+ return PackageState.Cancelled;
+ }
+
+ public static bool Uninstall(string id)
+ {
+ return LocalPackageManager.RemovePackage(id);
+ }
+
+ ///
+ /// Updates a package to the most recent version regardless of sdk version
+ ///
+ ///
+ /// true update succes, false update failed or cancelled
+ public static PackageState UpdateLatest(string id, out Package package)
+ {
+ package = null;
+
+ // check if is already installed
+ if (!LocalPackageManager.IsPackageInstalled(id, out Package localPackage, out string packageDirectory))
+ return PackageState.NotInstalled;
+
+ var remotePackage = RemotePackageManager.GetPackageInfo(id);
+
+ if (remotePackage == null)
+ return PackageState.NotExisting;
+
+ var asset = RemotePackageManager.GetAssetLatestVersion(remotePackage);
+
+ // You already have the latest version
+ if (localPackage.Assets[0].Version == asset.Version)
+ return PackageState.UpToDate;
+
+ if (validateSdkVersion(asset))
+ {
+ Uninstall(id);
+ return InstallPackage(remotePackage, asset);
+ }
+
+ return PackageState.Cancelled;
+ }
+
+ ///
+ /// Updates a package to the most recent version given an sdk version
+ ///
+ ///
+ /// true if update success, false if package is not installed
+ public static PackageState UpdateBySdkVersion(string id, out Package package, bool dontAsk)
+ {
+ package = null;
+
+ if (!LocalPackageManager.IsPackageInstalled(id, out Package localPackage, out string pd))
+ return PackageState.NotInstalled;
+
+ var remotePackage = RemotePackageManager.GetPackageInfo(id);
+
+ if (remotePackage == null)
+ return PackageState.NotExisting;
+
+ var asset = RemotePackageManager.GetAssetLatestVersionBySdkVersion(remotePackage);
+
+ // You already have the latest version
+ if (localPackage.Assets[0].Version == asset.Version)
+ return PackageState.UpToDate;
+
+ if (dontAsk || validateSdkVersion(asset))
+ {
+ Uninstall(id);
+ return InstallPackage(remotePackage, asset);
+ }
+
+ return PackageState.Cancelled;
+ }
+
+ private static bool validateSdkVersion(Asset asset)
+ {
+ if (asset.SdkVersion > Config.CurrentSdkVersion)
+ return askUser("You are installing a package which the sdk version is more recent than what you have. Would you like to continue?");
+ else if (asset.SdkVersion < Config.CurrentSdkVersion)
+ return askUser("You are installing a package which the sdk version is older than what you have. Would you like to continue?");
+
+ return true;
+ }
+
+ ///
+ /// Just asks the user something
+ ///
+ /// true yes, false no
+ private static bool askUser(string question)
+ {
+ Console.WriteLine($"{question} [Y][N]");
+ while (true)
+ {
+ char answer = char.ToLower(Console.ReadKey().KeyChar);
+
+ if (answer == 'n')
+ return false;
+ else if (answer == 'y')
+ return true;
+ }
+ }
+ }
+}
diff --git a/PMF/src/Managers/RemotePackageManager.cs b/PMF/src/Managers/RemotePackageManager.cs
new file mode 100644
index 0000000..9e5e8db
--- /dev/null
+++ b/PMF/src/Managers/RemotePackageManager.cs
@@ -0,0 +1,100 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Net;
+using System.Text;
+
+namespace PMF.Managers
+{
+ public static class RemotePackageManager
+ {
+ ///
+ /// Gets package info from the server along with ALL the assets in the json
+ ///
+ ///
+ /// The package object downloaded
+ public static Package GetPackageInfo(string id)
+ {
+ try
+ {
+ using (WebClient client = new WebClient())
+ {
+ string json = client.DownloadString($"{Config.RepositoryEndpoint}/{id}");
+ return JsonConvert.DeserializeObject(json);
+ }
+ }
+ catch (WebException)
+ {
+ Console.WriteLine("Couldn't download information from the server");
+ return null;
+ }
+ }
+
+ ///
+ /// Downloads a specific version of a certain package
+ ///
+ ///
+ /// The zip file which was downloaded
+ public static string DownloadAsset(string id, Asset asset)
+ {
+ using (WebClient client = new WebClient())
+ {
+ var zipPath = Path.Combine(Config.TemporaryFolder, id);
+ Directory.CreateDirectory(zipPath);
+ client.DownloadFile(asset.Url, Path.Combine(zipPath, asset.FileName));
+ foreach (var dependency in asset.Dependencies)
+ client.DownloadFile(dependency.Url, Path.Combine(zipPath, dependency.FileName));
+ return zipPath;
+ }
+ }
+
+ ///
+ /// Gets you the latest version of a package
+ ///
+ ///
+ /// The latest asset version of a given package
+ public static Asset GetAssetLatestVersion(Package package)
+ {
+ if (package == null)
+ throw new ArgumentNullException();
+ if (package.Assets.Count == 0)
+ throw new ArgumentNullException("asset count");
+
+ Asset ret_asset = null;
+ foreach (var asset in package.Assets)
+ {
+ if (ret_asset == null || ret_asset.Version < asset.Version)
+ ret_asset = asset;
+ }
+
+ return ret_asset;
+ }
+
+ ///
+ /// Gets you the latest version of a package given an SDK version
+ ///
+ ///
+ ///
+ /// The latest asset version of a given package and given SDK version
+ public static Asset GetAssetLatestVersionBySdkVersion(Package package)
+ {
+ if (package == null)
+ throw new ArgumentNullException();
+ if (package.Assets.Count == 0)
+ throw new ArgumentNullException("asset count");
+
+ Asset ret_asset = null;
+ foreach (var asset in package.Assets)
+ {
+ if (asset.SdkVersion == Config.CurrentSdkVersion)
+ {
+ if (ret_asset == null || ret_asset.Version < asset.Version)
+ ret_asset = asset;
+ }
+ }
+
+ return ret_asset;
+ }
+ }
+}
diff --git a/PMF/src/Package/Asset.cs b/PMF/src/Package/Asset.cs
new file mode 100644
index 0000000..9d9c75f
--- /dev/null
+++ b/PMF/src/Package/Asset.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public class Asset
+ {
+ // This ensures the version object is correctly converted
+ [JsonConverter(typeof(VersionConverter))]
+ public Version Version { get; set; }
+
+ [JsonConverter(typeof(VersionConverter))]
+ public Version SdkVersion { get; set; }
+
+ public string Checksum { get; set; }
+
+ public string FileName { get; set; }
+
+ public double FileSize { get; set; }
+
+ public string Url { get; set; }
+
+ public List Dependencies { get; set; }
+ }
+}
diff --git a/PMF/src/Package/Dependency.cs b/PMF/src/Package/Dependency.cs
new file mode 100644
index 0000000..4c384fc
--- /dev/null
+++ b/PMF/src/Package/Dependency.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public class Dependency
+ {
+ public string Checksum { get; set; }
+
+ public string FileName { get; set; }
+
+ public double FileSize { get; set; }
+
+ public string Url { get; set; }
+ }
+}
diff --git a/PMF/src/Package/Package.cs b/PMF/src/Package/Package.cs
new file mode 100644
index 0000000..97e9cf1
--- /dev/null
+++ b/PMF/src/Package/Package.cs
@@ -0,0 +1,58 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public class Package
+ {
+ public string ID { get; set; }
+
+ // This converts enum to string and vice versa when generating or parsing json
+ [JsonConverter(typeof(StringEnumConverter))]
+ public PackageType Type { get; set; }
+
+ public string Name { get; set; }
+
+ public string Author { get; set; }
+
+ public string Description { get; set; }
+
+ // If the package is a local one the list will only have one asset which is the version installed
+ public List Assets { get; set; }
+
+ public Asset GetAssetVersion(Version version)
+ {
+ if (version == null)
+ throw new ArgumentNullException();
+
+ foreach (var asset in Assets)
+ {
+ if (asset.Version == version)
+ return asset;
+ }
+
+ return null;
+ }
+
+ // A valid package must have:
+ // - an id
+ // - a type
+ // - a name
+ // - an author
+ // - a description
+ // - at least one asset
+ public bool IsValid()
+ {
+ return !string.IsNullOrEmpty(ID) &&
+ Type != PackageType.None &&
+ !string.IsNullOrEmpty(Name) &&
+ !string.IsNullOrEmpty(Author) &&
+ !string.IsNullOrEmpty(Description) &&
+ Assets.Count > 0;
+ }
+ }
+}
diff --git a/PMF/src/Package/PackageType.cs b/PMF/src/Package/PackageType.cs
new file mode 100644
index 0000000..b9c105a
--- /dev/null
+++ b/PMF/src/Package/PackageType.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public enum PackageType
+ {
+ //None is there but should be pretty much impossible to get, only if something bad happens with the json sent from the server
+ None,
+ Plugin,
+ Library
+ }
+}
diff --git a/PMF/src/PackageState.cs b/PMF/src/PackageState.cs
new file mode 100644
index 0000000..aa0b769
--- /dev/null
+++ b/PMF/src/PackageState.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace PMF
+{
+ public enum PackageState
+ {
+ // Local
+ NotInstalled,
+ UpToDate,
+ Installed,
+ AlreadyInstalled,
+
+ // Remote
+ NotExisting,
+ VersionNotFound,
+
+ Cancelled,
+ Failed
+ }
+}