/// <summary> /// Delete the directory /// </summary> /// <param name="path">Path to the directory</param> /// <returns>A task that represents the completion of the operation</returns> protected virtual async Task DeleteDirectoryAsync(string path) { path = GetVirtualPath(path); if (path == GetRootDirectory()) { throw new Exception(GetLanguageResource("E_CannotDeleteRoot")); } path = GetFullPath(path); if (!_fileProvider.DirectoryExists(path)) { throw new Exception(GetLanguageResource("E_DeleteDirInvalidPath")); } if (_fileProvider.GetDirectories(path).Length > 0 || _fileProvider.GetFiles(path).Length > 0) { throw new Exception(GetLanguageResource("E_DeleteNonEmpty")); } try { _fileProvider.DeleteDirectory(path); await HttpContext.Response.WriteAsync(GetSuccessResponse()); } catch { throw new Exception(GetLanguageResource("E_CannotDeleteDir")); } }
/// <summary> /// Delete plugins /// </summary> public virtual void DeletePlugins() { //get all uninstalled plugins (delete plugin only previously uninstalled) var pluginDescriptors = _pluginsInfo.PluginDescriptors.Where(descriptor => !descriptor.Installed).ToList(); //filter plugins need to delete pluginDescriptors = pluginDescriptors .Where(descriptor => _pluginsInfo.PluginNamesToDelete.Contains(descriptor.SystemName)).ToList(); if (!pluginDescriptors.Any()) { return; } //do not inject services via constructor because it'll cause circular references var localizationService = EngineContext.Current.Resolve <ILocalizationService>(); var customerActivityService = EngineContext.Current.Resolve <ICustomerActivityService>(); //delete plugins foreach (var descriptor in pluginDescriptors) { try { //try to delete a plugin directory from disk storage var pluginDirectory = _fileProvider.GetDirectoryName(descriptor.OriginalAssemblyFile); if (_fileProvider.DirectoryExists(pluginDirectory)) { _fileProvider.DeleteDirectory(pluginDirectory); } //remove plugin system name from the appropriate list _pluginsInfo.PluginNamesToDelete.Remove(descriptor.SystemName); //activity log customerActivityService.InsertActivity("DeletePlugin", string.Format(localizationService.GetResource("ActivityLog.DeletePlugin"), descriptor.SystemName)); } catch (Exception exception) { //log error var message = string.Format(localizationService.GetResource("Admin.Plugins.Errors.NotDeleted"), descriptor.SystemName); _logger.Error(message, exception); } } //save changes _pluginsInfo.Save(); }
/// <summary> /// Initialize /// </summary> /// <param name="applicationPartManager">Application part manager</param> /// <param name="config">Config</param> public static void Initialize(ApplicationPartManager applicationPartManager, NopConfig config) { if (applicationPartManager == null) { throw new ArgumentNullException(nameof(applicationPartManager)); } if (config == null) { throw new ArgumentNullException(nameof(config)); } using (new WriteLockDisposable(Locker)) { // TODO: Add verbose exception handling / raising here since this is happening on app startup and could // prevent app from starting altogether var pluginFolder = _fileProvider.MapPath(NopPluginDefaults.Path); _shadowCopyFolder = _fileProvider.MapPath(NopPluginDefaults.ShadowCopyPath); _reserveShadowCopyFolder = _fileProvider.Combine(_fileProvider.MapPath(NopPluginDefaults.ShadowCopyPath), $"{NopPluginDefaults.ReserveShadowCopyPathName}{DateTime.Now.ToFileTimeUtc()}"); var referencedPlugins = new List <PluginDescriptor>(); var incompatiblePlugins = new List <string>(); try { var installedPluginSystemNames = GetInstalledPluginNames(_fileProvider.MapPath(NopPluginDefaults.InstalledPluginsFilePath)); Debug.WriteLine("Creating shadow copy folder and querying for DLLs"); //ensure folders are created _fileProvider.CreateDirectory(pluginFolder); _fileProvider.CreateDirectory(_shadowCopyFolder); //get list of all files in bin var binFiles = _fileProvider.GetFiles(_shadowCopyFolder, "*", false); if (config.ClearPluginShadowDirectoryOnStartup) { //clear out shadow copied plugins foreach (var f in binFiles) { if (_fileProvider.GetFileName(f).Equals("placeholder.txt", StringComparison.InvariantCultureIgnoreCase)) { continue; } Debug.WriteLine("Deleting " + f); try { //ignore index.htm var fileName = _fileProvider.GetFileName(f); if (fileName.Equals("index.htm", StringComparison.InvariantCultureIgnoreCase)) { continue; } _fileProvider.DeleteFile(f); } catch (Exception exc) { Debug.WriteLine("Error deleting file " + f + ". Exception: " + exc); } } //delete all reserve folders foreach (var directory in _fileProvider.GetDirectories(_shadowCopyFolder, NopPluginDefaults.ReserveShadowCopyPathNamePattern)) { try { _fileProvider.DeleteDirectory(directory); } catch { //do nothing } } } //load description files foreach (var dfd in GetDescriptionFilesAndDescriptors(pluginFolder)) { var descriptionFile = dfd.Key; var pluginDescriptor = dfd.Value; //ensure that version of plugin is valid if (!pluginDescriptor.SupportedVersions.Contains(NopVersion.CurrentVersion, StringComparer.InvariantCultureIgnoreCase)) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } //some validation if (string.IsNullOrWhiteSpace(pluginDescriptor.SystemName)) { throw new Exception($"A plugin '{descriptionFile}' has no system name. Try assigning the plugin a unique name and recompiling."); } if (referencedPlugins.Contains(pluginDescriptor)) { throw new Exception($"A plugin with '{pluginDescriptor.SystemName}' system name is already defined"); } //set 'Installed' property pluginDescriptor.Installed = installedPluginSystemNames .FirstOrDefault(x => x.Equals(pluginDescriptor.SystemName, StringComparison.InvariantCultureIgnoreCase)) != null; try { var directoryName = _fileProvider.GetDirectoryName(descriptionFile); if (string.IsNullOrEmpty(directoryName)) { throw new Exception($"Directory cannot be resolved for '{_fileProvider.GetFileName(descriptionFile)}' description file"); } //get list of all DLLs in plugins (not in bin!) var pluginFiles = _fileProvider.GetFiles(directoryName, "*.dll", false) //just make sure we're not registering shadow copied plugins .Where(x => !binFiles.Select(q => q).Contains(x)) .Where(x => IsPackagePluginFolder(_fileProvider.GetDirectoryName(x))) .ToList(); //other plugin description info var mainPluginFile = pluginFiles .FirstOrDefault(x => _fileProvider.GetFileName(x).Equals(pluginDescriptor.AssemblyFileName, StringComparison.InvariantCultureIgnoreCase)); //plugin have wrong directory if (mainPluginFile == null) { incompatiblePlugins.Add(pluginDescriptor.SystemName); continue; } pluginDescriptor.OriginalAssemblyFile = mainPluginFile; //shadow copy main plugin file pluginDescriptor.ReferencedAssembly = PerformFileDeploy(mainPluginFile, applicationPartManager, config); //load all other referenced assemblies now foreach (var plugin in pluginFiles .Where(x => !_fileProvider.GetFileName(x).Equals(_fileProvider.GetFileName(mainPluginFile), StringComparison.InvariantCultureIgnoreCase)) .Where(x => !IsAlreadyLoaded(x))) { PerformFileDeploy(plugin, applicationPartManager, config); } //init plugin type (only one plugin per assembly is allowed) foreach (var t in pluginDescriptor.ReferencedAssembly.GetTypes()) { if (typeof(IPlugin).IsAssignableFrom(t)) { if (!t.IsInterface) { if (t.IsClass && !t.IsAbstract) { pluginDescriptor.PluginType = t; break; } } } } referencedPlugins.Add(pluginDescriptor); } catch (ReflectionTypeLoadException ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. "; foreach (var e in ex.LoaderExceptions) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } catch (Exception ex) { //add a plugin name. this way we can easily identify a problematic plugin var msg = $"Plugin '{pluginDescriptor.FriendlyName}'. {ex.Message}"; var fail = new Exception(msg, ex); throw fail; } } } catch (Exception ex) { var msg = string.Empty; for (var e = ex; e != null; e = e.InnerException) { msg += e.Message + Environment.NewLine; } var fail = new Exception(msg, ex); throw fail; } ReferencedPlugins = referencedPlugins; IncompatiblePlugins = incompatiblePlugins; } }
/// <summary> /// Upload single item from the archive into the physical directory /// </summary> /// <param name="archivePath">Path to the archive</param> /// <returns>Uploaded item descriptor</returns> protected virtual IDescriptor UploadSingleItem(string archivePath) { //get path to the plugins directory var pluginsDirectory = _fileProvider.MapPath(NopPluginDefaults.Path); //get path to the themes directory var themesDirectory = string.Empty; if (!string.IsNullOrEmpty(NopPluginDefaults.ThemesPath)) { themesDirectory = _fileProvider.MapPath(NopPluginDefaults.ThemesPath); } IDescriptor descriptor = null; string uploadedItemDirectoryName; using (var archive = ZipFile.OpenRead(archivePath)) { //the archive should contain only one root directory (the plugin one or the theme one) var rootDirectories = archive.Entries.Select(p => p.FullName.Split('/')[0]).Distinct().ToList(); if (rootDirectories.Count != 1) { throw new Exception("The archive should contain only one root plugin or theme directory. " + "For example, Payments.PayPalDirect or DefaultClean. " + $"To upload multiple items, the archive should have the '{NopPluginDefaults.UploadedItemsFileName}' file in the root"); } //get directory name (remove the ending /) uploadedItemDirectoryName = rootDirectories.First(); //try to get descriptor of the uploaded item foreach (var entry in archive.Entries) { //whether it's a plugin descriptor var isPluginDescriptor = entry.FullName .Equals($"{uploadedItemDirectoryName}/{NopPluginDefaults.DescriptionFileName}", StringComparison.InvariantCultureIgnoreCase); //or whether it's a theme descriptor var isThemeDescriptor = entry.FullName .Equals($"{uploadedItemDirectoryName}/{NopPluginDefaults.ThemeDescriptionFileName}", StringComparison.InvariantCultureIgnoreCase); if (!isPluginDescriptor && !isThemeDescriptor) { continue; } using var unzippedEntryStream = entry.Open(); using var reader = new StreamReader(unzippedEntryStream); //whether a plugin is upload if (isPluginDescriptor) { descriptor = PluginDescriptor.GetPluginDescriptorFromText(reader.ReadToEnd()); //ensure that the plugin current version is supported if (!((PluginDescriptor)descriptor).SupportedVersions.Contains(NopVersion.CURRENT_VERSION)) { throw new Exception($"This plugin doesn't support the current version - {NopVersion.CURRENT_VERSION}"); } } //or whether a theme is upload if (isThemeDescriptor) { descriptor = _themeProvider.GetThemeDescriptorFromText(reader.ReadToEnd()); } break; } } if (descriptor == null) { throw new Exception("No descriptor file is found. It should be in the root of the archive."); } if (string.IsNullOrEmpty(uploadedItemDirectoryName)) { throw new Exception($"Cannot get the {(descriptor is PluginDescriptor ? "plugin" : "theme")} directory name"); } //get path to upload var directoryPath = descriptor is PluginDescriptor ? pluginsDirectory : themesDirectory; var pathToUpload = _fileProvider.Combine(directoryPath, uploadedItemDirectoryName); //ensure it's a new directory (e.g. some old files are not required when re-uploading a plugin) //furthermore, zip extract functionality cannot override existing files //but there could deletion issues (related to file locking, etc). In such cases the directory should be deleted manually if (_fileProvider.DirectoryExists(pathToUpload)) { _fileProvider.DeleteDirectory(pathToUpload); } //unzip archive ZipFile.ExtractToDirectory(archivePath, directoryPath); return(descriptor); }