using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.Design; using System.Diagnostics.CodeAnalysis; using System.IO; using System.IO.MemoryMappedFiles; using System.Management.Instrumentation; using System.Reflection; using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using TWVSExtension.Commands; using TWVSExtension.Controls; using TWVSExtension.Enums; using TWVSExtension.Exceptions; using TWVSExtension.Model; using TWVSExtension.Package; using TWVSExtension.Repository; using TWVSExtension.Service; using TWVSExtension.ToolWindow; using TWVSExtension.Utils; using System.Text.Json; using Task = System.Threading.Tasks.Task; namespace TWVSExtension { /// /// This is the class that implements the package exposed by this assembly. /// /// /// /// The minimum requirement for a class to be considered a valid package for Visual Studio /// is to implement the IVsPackage interface and register itself with the shell. /// This package uses the helper classes defined inside the Managed Package Framework (MPF) /// to do it: it derives from the Package class that provides the implementation of the /// IVsPackage interface and uses the registration attributes defined in the framework to /// register itself and its components with the shell. These attributes tell the pkgdef creation /// utility what data to put into .pkgdef file. /// /// /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file. /// /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] [InstalledProductRegistration("TWVSSCM", "TallyWorld Source and Tools Control Management", "18.8.4")] // Info on this package for Help/About [Guid(TWVSSCMPackage.PackageGuidString)] [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "pkgdef, VS and vsixmanifest are valid VS terms")] [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)] [ProvideService(typeof(STWExtnOptionsService))] [ProvideOptionPage(typeof(TWVSExtensionsOptionPageGrid), "TWVSExtensions", "General Settings", 0, 0, true)] [ProvideToolWindow(typeof(TWRepoExplorerWindow), Style = VsDockStyle.Tabbed, Window = EnvDTE.Constants.vsWindowKindOutput)] [ProvideToolWindow(typeof(TWRepoDetailsWindow), Window = EnvDTE.Constants.vsWindowKindSolutionExplorer, Style = VsDockStyle.Tabbed, Transient = true)] [ProvideToolWindow(typeof(TWRepoFileHistoryWindow), Window = "DocumentWell", Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.none, Transient = true, MultiInstances = true)] [ProvideToolWindow(typeof(TWRepoFileBlameWindow), Window = "DocumentWell", Style = VsDockStyle.Tabbed, Orientation = ToolWindowOrientation.none, Transient = true, MultiInstances = true)] public class TWVSSCMPackage : TWAsyncPackage { /// /// TWVSExtensionPackage GUID string. /// public const string PackageGuidString = "55d531bf-08bf-449e-a46a-96916825372e"; public TWVSExtensionsOptionPageGrid OptionPageInstance { get; set; } public TWRepoDetailsWindowData TWRepoDetailsWindowData { get; set; } public TWRepoFileData TWRepoFileData { get; set; } public TWRepoExplorerWindowData TWRepoExplorerWindowData { get; } private bool CommandsInitialized; private bool disabled; public CancellationToken CancellationToken { get; private set; } public bool IsExtensionDisabled { get => disabled; } /// /// Initializes a new instance of the class. /// public TWVSSCMPackage() : base() { // Inside this method you can place any initialization code that does not require // any Visual Studio service because at this point the package object is created but // not sited yet inside Visual Studio environment. The place to do all the other // initialization is the Initialize method. //This is overall Extension service which provide loglevel and pmt functionality flag settings vai VSSTCM extension only //if this extension is not installed, log level is set to minimal and PMT functionality is disabled CurrentInstance = this; TWRepoExplorerWindowData = new TWRepoExplorerWindowData(this); } #region Package Members /// /// Initialization of the package; this method is called right after the package is sited, so this is the place /// where you can put all the initialization code that rely on services provided by VisualStudio. /// /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down. /// A provider for progress updates. /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method. /// protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) { this.CancellationToken = cancellationToken; // When initialized asynchronously, the current thread may be a background thread at this point. // Do any initialization that requires the UI thread after switching to the UI thread. await TWVSSCMInitializeAsync(cancellationToken, progress); } public async Task TWVSSCMInitializeAsync(CancellationToken cancellationToken, IProgress progress) { //string settingsKey = "TWRepoViews"; //int settingExists = 0; //int propertyExists = 0; //string repoView = ""; //string json = ""; //var storeManager = ServiceProvider.GlobalProvider.GetService(typeof(SVsSettingsManager)) as IVsSettingsManager; // //if (storeManager != null) //{ // storeManager.GetWritableSettingsStore((uint)__VsSettingsScope.SettingsScope_UserSettings, out IVsWritableSettingsStore settingsStore); // settingsStore.CollectionExists(settingsKey, out settingExists); // // if (settingExists == 1) // { // settingsStore.PropertyExists(settingsKey, "TWRepoView", out propertyExists); // if (propertyExists == 1) // { // settingsStore.GetString(settingsKey, "TWRepoView", out json); // repoView = json; // } // } //} // Was cancellation already requested? if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } try { progress.Report(new ServiceProgressData("Preparing TWVSSCM...")); TWTemporaryFileStorage.Init(); IProgress init_progress = null; // Was cancellation already requested? if (cancellationToken.IsCancellationRequested) { cancellationToken.ThrowIfCancellationRequested(); } try { ReportProgress(progress, init_progress, "Initializing Extension Data..."); await TWOutputPane.TWVSExtension.FocusAsync(); await TWVSSCMUtils.InitializeDataAsync(this, async (level, context, line, color) => { await TWUtils.ShowTextToExtensionPaneAsync(level, context, line, color); if (level < TWLogLevel.Diagnostic) { ReportProgress(progress, init_progress, context + " | " + line); } }); disabled = false; } catch (TWVSExtensionException e) { await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync(e, false); await TWUtils.ShowErrorAsync(InfoBarTarget.MainWindow, $"TWVSSCM menu items will be disabled till the following error is rectified : {e.Message} \\n\n Use menu 'Extensions->TWVSSCM->Re-Initialize Extension' to enable after fixing the error.", e); disabled = true; } catch (Exception e) { await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync(e, false); await TWUtils.ShowAlertBoxBeforeAbortAsync($"Unable to load TWVSSCM : {e.Message}.", e); disabled = true; } if (!CommandsInitialized) { ReportProgress(progress, init_progress, "Initializing command TWRepoExplorerToolWindowCommand ..."); await TWRepoExplorerToolWindowCommand.InitializeAsync(this); ReportProgress(progress, init_progress, "Initializing file context commands ..."); await TWFileContextCommands.InitializeAsync(this); CommandsInitialized = true; } ReportProgress(progress, init_progress, "Done."); } catch (Exception ex) { await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync("Package Init", ex, false); await TWUtils.ShowAlertBoxBeforeAbortAsync($"Unable to load TWVSSCM : {ex.Message}.", ex); } } public object CreateService(IServiceContainer container, Type serviceType) { if (typeof(STWExtnOptionsService) == serviceType) return new TWExtnOptionsService(this); return null; } private VSIXManifest currentManifest = null; public override VSIXManifest GetManifest() { if (currentManifest == null) { Assembly assembly = Assembly.GetExecutingAssembly(); currentManifest = TWUtils.CreateManifest(assembly); } return currentManifest; } private void ReportProgress(IProgress progress, IProgress init_progress, string report) { progress.Report(new ServiceProgressData(report)); if (init_progress != null) { ThreadedWaitDialogProgressData data = new ThreadedWaitDialogProgressData("Initializing TWVSSCM, please wait...", report, report); init_progress.Report(data); } } #endregion Package Members private TWVSExtensionsOptionPageGrid OptionPage { get { if (OptionPageInstance == null) { if (ThreadHelper.CheckAccess()) { OptionPageInstance = (TWVSExtensionsOptionPageGrid)GetDialogPage(typeof(TWVSExtensionsOptionPageGrid)); } } if (OptionPageInstance != null) { return OptionPageInstance; } else { TWVSExtensionsOptionPageGrid optionPage = new TWVSExtensionsOptionPageGrid(this); optionPage.LoadSettingsFromStorage(); return optionPage; } } } public void SetOptionPageInstance() { OptionPageInstance = (TWVSExtensionsOptionPageGrid)GetDialogPage(typeof(TWVSExtensionsOptionPageGrid)); OptionPageInstance.SetPackage(this); } public void SetOptionPageInstance(TWVSExtensionsOptionPageGrid option) { OptionPageInstance = option; } public void DisposeOptionPageInstance() { if (OptionPageInstance != null) { OptionPageInstance.Dispose(); OptionPageInstance = null; // Clear the reference to avoid memory leaks } } public void ShowOptionPageTask() { ThreadHelper.ThrowIfNotOnUIThread(); Type optionsPageType = typeof(TWVSExtensionsOptionPageGrid); OptionPageInstance.package.ShowOptionPage(optionsPageType); } public void ResetExtensionSettingFolder() { OptionPageInstance.ResetExtensionSettingFolder(); } public int WaitForPRCompletion { get { return OptionPage.WaitForPRCompletion; } } public GitPullRequestMergeType DefaultMergeType { get { return TWVSSCMUtils.DefaultMergeType; } } public string DevelopmentTrunk { get { return TWVSSCMUtils.DevelopmentTrunk; } } public TWLogLevel LogLevel { get { return OptionPage.LogLevel; } } public string BootstrapURL { get { return OptionPage.BootstrapURL; } } public bool UseGitCLI { get { return File.Exists(Path.Combine(TWUtils.WorkspaceHome, ".cli")); } } public CreatePR CreatePR { get { return OptionPage.CreatePR; } set { OptionPage.CreatePR = value; OptionPage.SaveSettingsToStorage(); } } public int SyncAllThreads { get { return OptionPage.SyncAllThreads; } } public SortingOrder[] RepoExpSorting => OptionPage.RepoExpSorting; public int CloneWaitTime { get { return OptionPage.CloneWaitTime; } } public WebOpenType OpeningURLTypeForPR { get { return OptionPage.OpeningURLTypeForPR; } } public ChangesView DefaultView { get { return OptionPage.DefaultView; } set { OptionPage.DefaultView = value; OptionPage.SaveSettingsToStorage(); } } private ConcurrentDictionary listToMonitor = new ConcurrentDictionary(); private void ObjectCreatedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e) { ObjectChangedExecutor(watcher, obj, e); } private void ObjectDeletedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e) { ObjectChangedExecutor(watcher, obj, e); } private void ObjectRenamedExecutor(FileSystemWatcher watcher, ObservableObject obj, RenamedEventArgs e) { ObjectChangedExecutor(watcher, obj, e); } private void ObjectChangedExecutor(FileSystemWatcher watcher, ObservableObject obj, FileSystemEventArgs e) { try { if (CancellationToken.IsCancellationRequested) { return; } if (OptionPage.DisableAutoRefresh) { return; } if (obj == null || obj.Observed == null || obj.Observed.Repository == null || obj.Observed.Repository.LocalDir == null) { return; } if (LibGit2Sharp.Repository.IsValid(obj.Observed.Repository.LocalDir) == false) { return; } DateTime dateTime = DateTime.Now; if (e.FullPath.Contains("\\.vs\\") || ".vs".Equals(e.Name)) { //ignore all changes under .vs folder, if the file belongs to any folder under ".vs" or .vs folder itself changed return; } if (".git".Equals(e.Name) || e.FullPath.StartsWith(Path.Combine(obj.Observed.Repository.LocalDir, ".git\\")) || e.FullPath.StartsWith(Path.Combine(obj.Observed.Repository.LocalDir, ".git/"))) { //Ignore all changes inside .git directory, excpet index changes if ( e.Name.Equals(Path.Combine(".git", "index")) || e.Name.Equals(Path.Combine(".git", "FETCH_HEAD")) || e.Name.Equals(Path.Combine(".git", "HEAD")) || e.Name.StartsWith(Path.Combine(".git", "refs", "heads")) //branch changes || e.Name.StartsWith(Path.Combine(".git", "refs", "remotes")) //remotes changes || e.Name.StartsWith(Path.Combine(".git", "refs", "tags")) //tags changes || e.Name.Equals(Path.Combine(".git", "refs", "stash")) //stash changes || e.Name.Equals(Path.Combine(".git", "packed-refs")) || e.Name.Equals(Path.Combine(".git", "packed-refs.lock")) //ref change is completed, lock file will be deleted ) { //Staging/commit/add changes index file //checkout changes HEAD, packed-refs and heads file //continue ... } else { //Ignore all other changes inside .git folder return; } } //Refresh sequentially obj.Observed.QueueJob(async () => { string extra = ""; if(e.ChangeType == WatcherChangeTypes.Renamed) { RenamedEventArgs r = ((RenamedEventArgs)e); extra = " from " + r.OldName; } await TWUtils.ShowTextToExtensionPaneAsync(TWLogLevel.Diagnostic, "Observer", $"{watcher.Path}\\{e.Name} {e.ChangeType}{extra}"); if (CancellationToken.IsCancellationRequested || (LibGit2Sharp.Repository.IsValid(obj.Observed.Repository.LocalDir) == false)) { return; } //watcher.EnableRaisingEvents = false; int newHashcode = 0; try { obj.Observed.Repository.UpdateProperties(false); newHashcode = obj.Observed.ComputeContentHashCode(); } finally { //watcher.EnableRaisingEvents = true; } if (obj.Hashcode != newHashcode) { try { //Object content changed obj.Observed.OnChange(DisposalToken); obj.Hashcode = newHashcode; if (obj.ParentList != null) obj.ParentList.RefreshList(obj.Observed); } catch (Exception ex) { await TWUtils.PrintExceptionToOutputPaneAndErrorLogAsync("Change Monitor", ex, false); } } }); } catch (Exception ex) { TWUtils.PrintExceptionToOutputPaneAndErrorLog("Change Monitor", ex, false); } } public void StartWatcher(ObservableObject obj) { FileSystemWatcher watcher = new FileSystemWatcher(obj.Observed.Repository.LocalDir); watcher.InternalBufferSize = 64 * 1024; //64Kb watcher.NotifyFilter = // NotifyFilters.Attributes //not interested in attribute NotifyFilters.CreationTime | NotifyFilters.DirectoryName | NotifyFilters.FileName // | NotifyFilters.LastAccess //not interested in read | NotifyFilters.LastWrite // | NotifyFilters.Security //not interested in security attribute changes // | NotifyFilters.Size; //Size event need not be monitored, last write is enough ; watcher.Changed += (sender, e) => ObjectChangedExecutor(sender as FileSystemWatcher, obj, e); watcher.Created += (sender, e) => ObjectCreatedExecutor(sender as FileSystemWatcher, obj, e); watcher.Deleted += (sender, e) => ObjectDeletedExecutor(sender as FileSystemWatcher, obj, e); watcher.Renamed += (sender, e) => ObjectRenamedExecutor(sender as FileSystemWatcher, obj, e); watcher.Error += (sender, e) => TWUtils.PrintExceptionToOutputPaneAndErrorLog(e.GetException(), false); watcher.Filter = obj.Observed.FilePattern; watcher.IncludeSubdirectories = true; watcher.EnableRaisingEvents = true; obj.Watcher = watcher; } public Guid RegisterMonitor(TWObservableList objectsToMonitor) { Guid id = Guid.NewGuid(); listToMonitor[id] = objectsToMonitor; return id; } public void PauseMonitors() { foreach(TWObservableList objectsToMonitor in listToMonitor.Values) { foreach (ObservableObject obj in objectsToMonitor.ObservableItems) { if (obj.Watcher != null) { obj.Watcher.EnableRaisingEvents = false; } } } } public void ResumeMonitors() { foreach (TWObservableList objectsToMonitor in listToMonitor.Values) { foreach (ObservableObject obj in objectsToMonitor.ObservableItems) { if (obj.Watcher != null) { obj.Watcher.EnableRaisingEvents = true; } } } } public void UnregisterMonitor(Guid id) { if (listToMonitor.TryRemove(id, out TWObservableList objectsToMonitor)) { foreach (ObservableObject obj in objectsToMonitor.ObservableItems) { if (obj.Watcher != null) { obj.Watcher.EnableRaisingEvents = false; obj.Watcher.Dispose(); obj.Watcher = null; } } } } public override IVsAsyncToolWindowFactory GetAsyncToolWindowFactory(Guid toolWindowType) { return this; } protected override async Task InitializeToolWindowAsync(Type toolWindowType, int id, CancellationToken cancellationToken) { if (toolWindowType.Equals(typeof(TWRepoFileHistoryWindow))) { return TWRepoFileData; } if (toolWindowType.Equals(typeof(TWRepoFileBlameWindow))) { return TWRepoFileData; } if (toolWindowType.Equals(typeof(TWRepoDetailsWindow))) { return TWRepoDetailsWindowData; } if (toolWindowType.Equals(typeof(TWRepoExplorerWindow))) { return TWRepoExplorerWindowData; } await TWUtils.ShowTextToExtensionPaneAsync(TWLogLevel.Minimal, "InitializeToolWindowAsync", "Unknown Tool Window Type: " + toolWindowType); return null; } public override void Dispose() { TWRepository.DisposeAll(); TWTemporaryFileStorage.Dispose(); TWPendingRepoDeletion.Dispose(); } public void SaveSettingsToStorage() { OptionPage.SaveSettingsToStorage(); } public void SaveRepoExpSorting(SortingOrder[] sortingOrders) { OptionPage.RepoExpSorting = sortingOrders; OptionPage.SaveSettingsToStorage(); } public static TWVSSCMPackage CurrentInstance; } }