using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using NuGet.Versioning; using Velopack.Logging; namespace Velopack.Locators { /// /// A base class describing where Velopack can find key folders and files. /// public abstract class VelopackLocator : IVelopackLocator { private static IVelopackLocator? _current; /// /// The default log file name for Velopack. /// protected const string DefaultLoggingFileName = "velopack.log"; /// /// Check if a VelopackLocator has been set for the current process. /// public static bool IsCurrentSet => _current != null; /// /// Get the current locator in use, this process-wide locator can be set/overridden during VelopackApp.Build(). /// Alternatively, most methods which use locators also accept an IVelopackLocator as a parameter. /// public static IVelopackLocator Current { get { if (_current == null) throw new InvalidOperationException( $"No VelopackLocator has been set. Either call {nameof(VelopackApp)}.{nameof(VelopackApp.Build)}() or provide {nameof(IVelopackLocator)} as a method parameter."); return _current; } } /// Create a new default locator based on the current operating system. public static IVelopackLocator CreateDefaultForPlatform(IProcessImpl? processImpl = null, IVelopackLogger? logger = null) { if (VelopackRuntimeInfo.IsWindows) return _current = new WindowsVelopackLocator(processImpl, logger); if (VelopackRuntimeInfo.IsOSX) return _current = new OsxVelopackLocator(processImpl, logger); if (VelopackRuntimeInfo.IsLinux) return _current = new LinuxVelopackLocator(processImpl, logger); throw new PlatformNotSupportedException($"OS platform '{VelopackRuntimeInfo.SystemOs.GetOsLongName()}' is not supported."); } internal static void SetCurrentLocator(IVelopackLocator locator) { _current = locator; } internal static IVelopackLocator GetCurrentOrCreateDefault(IProcessImpl? processImpl = null, IVelopackLogger? logger = null) { _current ??= CreateDefaultForPlatform(processImpl, logger); return _current; } /// public abstract string? AppId { get; } /// public abstract string? RootAppDir { get; } /// public abstract string? PackagesDir { get; } /// public virtual string? AppTempDir => CreateSubDirIfDoesNotExist(PackagesDir, "VelopackTemp"); /// public abstract string? UpdateExePath { get; } /// public abstract string? AppContentDir { get; } /// public abstract string? Channel { get; } /// public abstract IVelopackLogger Log { get; } /// public virtual bool IsPortable => false; /// public abstract IProcessImpl Process { get; } /// public virtual string? ThisExeRelativePath { get { if (AppContentDir == null) return null; var path = Process.GetCurrentProcessPath(); if (path.StartsWith(AppContentDir, StringComparison.OrdinalIgnoreCase)) { return path.Substring(AppContentDir.Length + 1); } else { throw new InvalidOperationException(path + " is not contained in " + AppContentDir); } } } /// public abstract SemanticVersion? CurrentlyInstalledVersion { get; } /// public virtual List GetLocalPackages() { try { if (CurrentlyInstalledVersion == null) return new List(0); var list = new List(); if (PackagesDir is { } packagesDir) { foreach (var pkg in Directory.EnumerateFiles(packagesDir, "*.nupkg")) { try { var asset = VelopackAsset.FromNupkg(pkg); if (asset?.Version != null) { list.Add(asset); } } catch (Exception ex) { Log.Warn(ex, $"Error while reading local package '{pkg}'."); } } } return list; } catch (Exception ex) { Log.Error(ex, "Error while reading local packages."); return new List(0); } } /// public virtual VelopackAsset? GetLatestLocalFullPackage() { return GetLocalPackages() .OrderByDescending(x => x.Version) .FirstOrDefault(a => a.Type == VelopackAssetType.Full); } /// /// Given a base dir and a directory name, will create a new sub directory of that name. /// Will return null if baseDir is null, or if baseDir does not exist. /// protected static string? CreateSubDirIfDoesNotExist(string? baseDir, string? newDir) { if (String.IsNullOrEmpty(baseDir) || string.IsNullOrEmpty(newDir)) return null; var infoBase = new DirectoryInfo(baseDir); if (!infoBase.Exists) return null; var info = new DirectoryInfo(Path.Combine(baseDir, newDir)); if (!info.Exists) info.Create(); return info.FullName; } /// public Guid? GetOrCreateStagedUserId() { if (PackagesDir == null) return null; var stagedUserIdFile = Path.Combine(PackagesDir, ".betaId"); Guid ret; if (File.Exists(stagedUserIdFile)) { try { if (!Guid.TryParse(File.ReadAllText(stagedUserIdFile, Encoding.UTF8), out ret)) { throw new Exception("File was read but contents were invalid"); } Log.Info($"Loaded existing staging userId: {ret} from {stagedUserIdFile}"); return ret; } catch (Exception ex) { Log.Debug(ex, "Couldn't read staging userId, creating a new one"); } } else { Log.Warn($"No staging userId in file '{stagedUserIdFile}', creating a new one."); } ret = Guid.NewGuid(); try { File.WriteAllText(stagedUserIdFile, ret.ToString("N"), Encoding.UTF8); Log.Info($"Generated new staging userId: {ret}"); return ret; } catch (Exception ex) { Log.Warn(ex, "Couldn't write out staging userId."); return null; } } } }