From be9ef02fb5df4bdb4e8c7f8dd8c48e543f2ddadd Mon Sep 17 00:00:00 2001 From: John Hodnik Date: Thu, 30 May 2019 12:36:28 -0400 Subject: [PATCH] First commit --- .gitignore | 338 ++++++++++++++++++++++++++++ LiveSync.sln | 25 ++ LiveSync/App.config | 6 + LiveSync/FileStructure.cs | 142 ++++++++++++ LiveSync/FolderPair.cs | 322 ++++++++++++++++++++++++++ LiveSync/LiveSync.csproj | 55 +++++ LiveSync/Program.cs | 41 ++++ LiveSync/Properties/AssemblyInfo.cs | 36 +++ 8 files changed, 965 insertions(+) create mode 100644 .gitignore create mode 100644 LiveSync.sln create mode 100644 LiveSync/App.config create mode 100644 LiveSync/FileStructure.cs create mode 100644 LiveSync/FolderPair.cs create mode 100644 LiveSync/LiveSync.csproj create mode 100644 LiveSync/Program.cs create mode 100644 LiveSync/Properties/AssemblyInfo.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6a1d426 --- /dev/null +++ b/.gitignore @@ -0,0 +1,338 @@ +## 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 + +# 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 + +# 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 +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.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 +# 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 + +# 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/ +# ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true +**/wwwroot/lib/ + +# 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 + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# 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/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# 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/ \ No newline at end of file diff --git a/LiveSync.sln b/LiveSync.sln new file mode 100644 index 0000000..d825e89 --- /dev/null +++ b/LiveSync.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.539 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveSync", "LiveSync\LiveSync.csproj", "{B31792CA-19D6-4218-A3FB-A0F81242100E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B31792CA-19D6-4218-A3FB-A0F81242100E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B31792CA-19D6-4218-A3FB-A0F81242100E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B31792CA-19D6-4218-A3FB-A0F81242100E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B31792CA-19D6-4218-A3FB-A0F81242100E}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3DD40AA7-F59F-4C81-A9F2-54A720029957} + EndGlobalSection +EndGlobal diff --git a/LiveSync/App.config b/LiveSync/App.config new file mode 100644 index 0000000..74ade9d --- /dev/null +++ b/LiveSync/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/LiveSync/FileStructure.cs b/LiveSync/FileStructure.cs new file mode 100644 index 0000000..695e5e9 --- /dev/null +++ b/LiveSync/FileStructure.cs @@ -0,0 +1,142 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LiveSync +{ + public class FileStructure : IDisposable + { + public string Path { get; private set; } + //public RecursiveFileListResult Result { get; private set; } = n + + public FileSystemWatcher Watcher { get; internal set; } = null; + + public event FileSystemEventHandler FileEvent; + public event RenamedEventHandler Renamed; + public event ErrorEventHandler Error; + + + public FileStructure(string path) + { + Path = path; + Watcher = new FileSystemWatcher(path); + + Watcher.IncludeSubdirectories = true; + Watcher.Changed += (x, y) => { FileEvent?.Invoke(x, y); }; + Watcher.Created += (x, y) => { FileEvent?.Invoke(x, y); }; + Watcher.Deleted += (x, y) => { FileEvent?.Invoke(x, y); }; + Watcher.Renamed += (x, y) => { Renamed?.Invoke(x, y); }; + Watcher.Error += (x, y) => { Error?.Invoke(x, y); }; + } + + public RecursiveFileListResult GetFiles() + { + return RecursiveFileList(Path); + } + + public static bool FileCompare(string path1, string path2) + { + if (!File.Exists(path1)) + { + throw new FileNotFoundException("File does not exist.", path1); + } + if (!File.Exists(path2)) + { + throw new FileNotFoundException("File does not exist.", path2); + } + FileInfo info1 = new FileInfo(path1); + FileInfo info2 = new FileInfo(path2); + + //if(info1.CreationTime != info2.CreationTime) + //{ + // return false; + //} + + if(Math.Abs(info1.LastWriteTime.Subtract(info2.LastWriteTime).TotalSeconds) >= 10) + { + return false; + } + + return true; + } + + public static RecursiveFileListResult RecursiveFileList(string path) + { + List ret = new List(); + List errs = new List(); + ret.Add(path); + try + { + ret.AddRange(Directory.GetFiles(path)); + foreach (string d in Directory.GetDirectories(path)) + { + var next = RecursiveFileList(d); + ret.AddRange(next.Results); + errs.AddRange(next.Exceptions); + } + } + catch (System.Exception ex) + { + errs.Add(ex); + } + + return new RecursiveFileListResult(ret.ToArray(), errs.ToArray()); + } + public static bool IsFileLocked(string filePath, FileAccess access) + { + if (!File.Exists(filePath)) { return false; } + FileStream stream = null; + + FileInfo file = new FileInfo(filePath); + try + { + stream = file.Open(FileMode.Open, access, FileShare.None); + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + return true; + } + finally + { + if (stream != null) + stream.Close(); + } + + //file is not locked + return false; + } + + public void Dispose() + { + Watcher.Dispose(); + } + + public static bool IsFile(string path) + { + FileAttributes attr = File.GetAttributes(path); + + //detect whether its a directory or file + return !((attr & FileAttributes.Directory) == FileAttributes.Directory); + } + + public class RecursiveFileListResult + { + public RecursiveFileListResult(string[] results, Exception[] exceptions) + { + Results = results; + Exceptions = exceptions; + } + + public string[] Results { get; private set; } + public Exception[] Exceptions { get; private set; } + } + } +} diff --git a/LiveSync/FolderPair.cs b/LiveSync/FolderPair.cs new file mode 100644 index 0000000..8894b34 --- /dev/null +++ b/LiveSync/FolderPair.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LiveSync +{ + public class FolderPair + { + public string Path1 { get; private set; } + public string Path2 { get; private set; } + + FileStructure fs1 = null; + FileStructure fs2 = null; + + public FolderPair(string path1, string path2) + { + Path1 = (new DirectoryInfo(path1).FullName); + Path2 = (new DirectoryInfo(path2).FullName); + fs1 = new FileStructure(Path1); + fs1.FileEvent += Fs1_FileEvent; + fs1.Renamed += Fs1_Renamed; + fs1.Error += Fs1_Error; + + fs2 = new FileStructure(Path2); + fs2.FileEvent += Fs2_FileEvent; + fs2.Renamed += Fs2_Renamed; + fs2.Error += Fs2_Error; + } + + public void Start() + { + ManualSync(); + + fs1.Watcher.EnableRaisingEvents = true; + fs2.Watcher.EnableRaisingEvents = true; + } + + class PendingOverwrite { + public string File1 { get; set; } + public string File2 { get; set; } + } + + List pendingOverwrites = new List(); + private void ProcessFileFolderChanges(bool fs1ToFs2, FileSystemEventArgs e) + { + try + { + string newPath = ""; + if (fs1ToFs2) + { + newPath = Path1ToPath2(e.FullPath); + } + else + { + newPath = Path2ToPath1(e.FullPath); + } + if (e.ChangeType == WatcherChangeTypes.Created || e.ChangeType == WatcherChangeTypes.Changed) + { + if (FileStructure.IsFile(e.FullPath)) + { + if (File.Exists(newPath)) + { + if (FileStructure.FileCompare(newPath, e.FullPath)) + { + return; + } + } + Directory.CreateDirectory(Path.GetDirectoryName(newPath)); + + + lock (pendingOverwrites) + { + if (pendingOverwrites.Any(x => x.File1 == e.FullPath && x.File2 == newPath)) + { + return; + } + pendingOverwrites.Add(new PendingOverwrite() { File1 = e.FullPath, File2 = newPath }); + } + Task.Factory.StartNew(() => + { + //Wait for file to become available + var newPathLocked = FileStructure.IsFileLocked(newPath, FileAccess.ReadWrite); + var oldPathLocked = FileStructure.IsFileLocked(e.FullPath, FileAccess.Read); + while (oldPathLocked || newPathLocked) + { + Thread.Sleep(100); + newPathLocked = FileStructure.IsFileLocked(newPath, FileAccess.ReadWrite); + oldPathLocked = FileStructure.IsFileLocked(e.FullPath, FileAccess.Read); + } + File.Copy(e.FullPath, newPath, true); + Console.WriteLine($"{e.FullPath} was {e.ChangeType}. Copied to {newPath}"); + + lock (pendingOverwrites) + { + pendingOverwrites.RemoveAll(x => x.File1 == e.FullPath && x.File2 == newPath); + } + }); + } + else + { + if (e.ChangeType == WatcherChangeTypes.Created) + { + if (!Directory.Exists(newPath)) + { + Directory.CreateDirectory(newPath); + Console.WriteLine($"New directory created {e.FullPath}. Created {newPath}"); + } + } + if (e.ChangeType == WatcherChangeTypes.Changed) + { + Console.WriteLine($"Folder change: {e.FullPath}"); + } + } + } + else if (e.ChangeType == WatcherChangeTypes.Deleted) + { + if (File.Exists(newPath) || Directory.Exists(newPath)) + { + if (FileStructure.IsFile(newPath)) + { + File.Delete(newPath); + Console.WriteLine($"{e.FullPath} was deleted. Deleted {newPath}"); + } + else + { + Directory.Delete(newPath); + Console.WriteLine($"{e.FullPath} was deleted. Deleted {newPath}"); + } + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error processing change event for {e.FullPath}: {ex}"); + } + } + private void ProcessFileRenamed(bool isFs1, RenamedEventArgs e) + { + try + { + + var newPath = ""; + var oldPath = ""; + + if (isFs1) + { + newPath = Path1ToPath2(e.FullPath); + oldPath = Path1ToPath2(e.OldFullPath); + } + else + { + newPath = Path2ToPath1(e.FullPath); + oldPath = Path2ToPath1(e.OldFullPath); + } + + if (File.Exists(newPath) || Directory.Exists(newPath)) + { + return; + } + + if (FileStructure.IsFile(oldPath)) + { + File.Move(oldPath, newPath); + } + else + { + Directory.Move(oldPath, newPath); + } + Console.WriteLine($"{e.FullPath} was renamed. Renamed {oldPath} to {newPath}"); + } + catch (Exception ex) + { + Console.WriteLine($"Unable to rename equivliant on FS2 {e.OldFullPath}: {ex}"); + } + } + + private void Fs1_Error(object sender, ErrorEventArgs e) + { + Console.WriteLine($"Error with Path1 watcher: {e.GetException()}"); + } + private void Fs1_Renamed(object sender, RenamedEventArgs e) + { + ProcessFileRenamed(true, e); + } + + + private void Fs1_FileEvent(object sender, FileSystemEventArgs e) + { + ProcessFileFolderChanges(true, e); + } + + private void Fs2_Error(object sender, ErrorEventArgs e) + { + Console.WriteLine($"Error with Path2 watcher: {e.GetException()}"); + } + private void Fs2_Renamed(object sender, RenamedEventArgs e) + { + ProcessFileRenamed(false, e); + } + private void Fs2_FileEvent(object sender, FileSystemEventArgs e) + { + ProcessFileFolderChanges(false, e); + } + + + private string Path1ToPath2(string path1Path) + { + var newPath = path1Path.Replace(Path1, Path2); + if (newPath == path1Path) + { + throw new Exception("Error converting path1 to path2."); + } + return newPath; + } + private string Path2ToPath1(string path2Path) + { + var newPath = path2Path.Replace(Path2, Path1); + if (newPath == path2Path) + { + throw new Exception("Error converting path2 to path1."); + } + return newPath; + } + + + /// + /// Syncs Path1 to Path2 + /// + public void ManualSync() + { + Console.WriteLine("Beginning manual sync..."); + FileStructure.RecursiveFileListResult res1 = null; + FileStructure.RecursiveFileListResult res2 = null; + Task ret1 = Task.Factory.StartNew(() => + { + res1 = fs1.GetFiles(); + Console.WriteLine($"Finished indexing {Path1}."); + }); + + Task ret2 = Task.Factory.StartNew(() => + { + res2 = fs2.GetFiles(); + Console.WriteLine($"Finished indexing {Path2}."); + }); + + //Wait for both threads to end. + ret1.Wait(); + ret2.Wait(); + + + //Check for new files on FS1, copy them over. + foreach (var file in res1.Results) + { + var oldDir = Path.GetDirectoryName(file); + //var newPath = file.Replace(Path1, Path2); + var newPath = Path1ToPath2(file); + //if (newPath == file) + //{ + // Console.WriteLine($"Error getting new path: {newPath}"); + // continue; + //} + var newDir = Path.GetDirectoryName(newPath); + + if (!Directory.Exists(newDir)) + { + Directory.CreateDirectory(newDir, Directory.GetAccessControl(oldDir)); + } + + if (FileStructure.IsFile(file)) + { + if (!File.Exists(newPath)) + { + File.Copy(file, newPath); + } + else if (!FileStructure.FileCompare(file, newPath)) + { + File.Copy(file, newPath, true); + } + } + else + { + if (!Directory.Exists(newPath)) + { + Directory.CreateDirectory(newPath, Directory.GetAccessControl(file)); + } + } + } + + //Make sure fiels on FS2 exist on FS1. If FS2 files dont' exist on FS1, delete them. + foreach(var file in res2.Results) + { + try + { + var newPath = Path2ToPath1(file); + if (!File.Exists(newPath) && !Directory.Exists(newPath)) + { + if (FileStructure.IsFile(file)) + { + File.SetAttributes(file, FileAttributes.Normal); + File.Delete(file); + } + else + { + Directory.Delete(file); + } + Console.WriteLine($"{file} does not exist on FS1, deleting."); + } + }catch(Exception ex) + { + Console.WriteLine($"Error verifying existance of {file}: {ex}"); + } + } + + + Console.WriteLine("Manual sync complete."); + } + } +} diff --git a/LiveSync/LiveSync.csproj b/LiveSync/LiveSync.csproj new file mode 100644 index 0000000..0fc7603 --- /dev/null +++ b/LiveSync/LiveSync.csproj @@ -0,0 +1,55 @@ + + + + + Debug + AnyCPU + {B31792CA-19D6-4218-A3FB-A0F81242100E} + Exe + LiveSync + LiveSync + v4.0 + 512 + true + true + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/LiveSync/Program.cs b/LiveSync/Program.cs new file mode 100644 index 0000000..a952a2c --- /dev/null +++ b/LiveSync/Program.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace LiveSync +{ + class Program + { + static void Main(string[] args) + { + //Use GetDirectoryName to fix capitilization. + //var path1 = (new DirectoryInfo(@"\\aetyrdata\D$").FullName); + //var path2 = (new DirectoryInfo(@"E:\").FullName); + + var path1 = @"H:\"; + var path2 = @"C:\Users\JHodnik\Desktop\hdrive\"; + + FolderPair pair = new FolderPair(path1, path2); + pair.Start(); + + while (true) + { + Thread.Sleep(1000); + } + } + + private static void Watcher_Error(object sender, ErrorEventArgs e) + { + Console.WriteLine($"ERROR: {e.GetException().ToString()}"); + } + + private static void Watcher_Event(object sender, FileSystemEventArgs e) + { + Console.WriteLine($"{e.ChangeType}: {e.FullPath}"); + } + } +} diff --git a/LiveSync/Properties/AssemblyInfo.cs b/LiveSync/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6f2ed27 --- /dev/null +++ b/LiveSync/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("LiveSync")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("LiveSync")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("b31792ca-19d6-4218-a3fb-a0f81242100e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")]