├── .gitattributes ├── .gitignore ├── README.md └── src ├── Wikiled.YiScanner.Tests ├── Client │ ├── Predicates │ │ └── NewFilesPredicateTests.cs │ ├── VideoHeaderExtensionsTests.cs │ └── VideoHeaderTests.cs ├── Data │ ├── Test.txt │ └── centaur_1.mpg ├── Destinations │ ├── ChainedActionDestinationTests.cs │ ├── ChainedPostActionDestinationTests.cs │ ├── CompressedDestinationTests.cs │ ├── FileDestinationTests.cs │ ├── PictureFileDestinationTests.cs │ └── TransformedDestinationTests.cs ├── Monitoring │ ├── MonitoringInstanceTests.cs │ └── Source │ │ └── SourceFactoryTests.cs ├── Network │ └── NetworkScannerTests.cs └── Wikiled.YiScanner.Tests.csproj ├── YiScanner.sln └── YiScanner ├── Client ├── Archive │ ├── DeleteArchiving.cs │ └── IDeleteArchiving.cs ├── FileMask.cs ├── Predicates │ ├── IPredicate.cs │ ├── NewFilesPredicate.cs │ └── NullPredicate.cs ├── VideoHeader.cs └── VideoHeaderExtensions.cs ├── Commands ├── BaseCommand.cs ├── DownloadCommand.cs └── MonitorCommand.cs ├── Destinations ├── ActionConfig.cs ├── ActionType.cs ├── ChainedPostActionDestination.cs ├── ChainedPriorActionDestination.cs ├── ChainedPriorActionDestinationBase.cs ├── CompressPriorAction.cs ├── ExecutePostAction.cs ├── FileDestination.cs ├── IDestination.cs ├── IPostAction.cs ├── IPriorAction.cs ├── PictureFileDestination.cs └── TransformedDestination.cs ├── Downloader ├── FileDownloader.cs ├── FtpDownloader.cs └── IDownloader.cs ├── Helpers └── ObservableExtensions.cs ├── Monitoring ├── Config │ ├── AutoDiscoveryConfig.cs │ ├── FtpConfig.cs │ ├── MonitoringConfig.cs │ ├── OutputConfig.cs │ ├── PredefinedCameraConfig.cs │ └── ServerConfig.cs ├── LiveDestinationFactory.cs ├── MonitoringInstance.cs ├── ServiceStarter.cs └── Source │ ├── DynamicHostManager.cs │ ├── Host.cs │ ├── HostTracking.cs │ ├── IHostManager.cs │ ├── ISourceFactory.cs │ ├── SourceFactory.cs │ └── StaticHostManager.cs ├── Network ├── INetworkScanner.cs └── NetworkScanner.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Server ├── FtpLogForNLog.cs ├── FtpLogManager.cs ├── IServerManager.cs └── ServerManager.cs ├── Wikiled.YiScanner.csproj ├── nlog.config └── service.json /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yi Camera FTP Scanner and Video Clips/Images Downloader with embedded FTP Server for other Cameras 2 | 3 | - **Automatic** network scanning for YI Cameras 4 | - **Embedded** FTP server for other cameras 5 | 6 | The application supports two modes - arguments explicitly specified in command prompt and configuration based. 7 | It can also run as Windows service. 8 | 9 | [![GitHub release](https://img.shields.io/github/release/AndMu/YiScanner.svg)](https://github.com/AndMu/YiScanner/releases) 10 | 11 | ## Configuration via service.json 12 | 13 | ``` 14 | { 15 | "Scan": 30, 16 | "Timeout": 1200, 17 | "Archive": 2, 18 | "Output": { 19 | "Compress": false, 20 | "Images": false, 21 | "Out": "D:/Monitor" 22 | }, 23 | "AutoDiscovery": { 24 | "On": true, 25 | "NetworkMask": "192.168.0.0/255.255.255.0" 26 | }, 27 | 28 | "YiFtp": { 29 | "Path": "/tmp/sd/record/", 30 | "Password": "", 31 | "Login": "root", 32 | "FileMask": "*.mp4" 33 | } 34 | } 35 | ``` 36 | 37 | - **Scan** - frequency of FTP scan (in seconds) 38 | - **Timeout** - FTP scan Timeout(hard) (in seconds) 39 | - **Archive** - delete previously downloaded old files. Number specifies how many days you want to keep history. 40 | 41 | ## Output 42 | 43 | - **Compress** - do you want to compress files 44 | - **Out** - location of downloaded files 45 | - **Images** - Download as a snapshot or video 46 | - **Archive** - delete previously downloaded old files. Number specifies how many days you want to keep history. 47 | 48 | ## Yi FTP Details 49 | 50 | - **Path** - Where images are stored 51 | - **Login** - Login 52 | - **FileMask** - Files to download 53 | - **Password** - Password 54 | 55 | ## Embedded FTP 56 | 57 | - **Path** - Local sub-folder where images will be stored 58 | - **Port** - Server port 59 | 60 | 61 | ## Actions on image 62 | 63 | If you want some action to be executed on each retrieved image: 64 | ``` 65 | "Action":{ 66 | "Type": "Execute", 67 | "Cmd": "%1", 68 | "Payload": null 69 | } 70 | ``` 71 | 72 | ## Manual YI Camera setup 73 | 74 | ``` 75 | { 76 | "Scan": 30, 77 | "Timeout": 1200, 78 | "Archive": 2, 79 | "Output": { 80 | "Compress": false, 81 | "Images": false, 82 | "Out": "D:/Monitor" 83 | }, 84 | "YiFtp": { 85 | "Path": "/tmp/sd/record/", 86 | "Password": "", 87 | "Login": "root", 88 | "FileMask": "*.mp4" 89 | }, 90 | "Known":{ 91 | "Cameras": "1080i", 92 | "Hosts": "192.168.0.22" 93 | } 94 | } 95 | ``` 96 | 97 | If you don't want automatic camera discovery, you can predefine list of cameras. 98 | 99 | - **Cameras** - list of cameras 100 | - **Hosts** - list of hosts. 101 | 102 | ## Install as Windows service 103 | ``` 104 | Wikiled.YiScanner.exe install 105 | ``` 106 | 107 | ## Running with arguments 108 | 109 | ### Monitoring and downloading latest clips 110 | 111 | ``` 112 | Wikiled.YiScanner.exe Monitor -Cameras=1080i -Hosts=192.168.0.202 [-Compress] -Out=c:\out -Scan=10 [-Archive=2] 113 | ``` 114 | 115 | ### Download once files, which haven't been downloaded yet 116 | 117 | ``` 118 | Wikiled.YiScanner.exe Download -Cameras=1080i -Hosts=192.168.0.202 [-Compress] -Out=c:\out [-Archive=2] 119 | ``` 120 | 121 | Options: 122 | - **Cameras** - list of cameras 123 | - **Hosts** - list of hosts. 124 | - **Compress** - do you want to compress files 125 | - **Out** - location of downloaded files 126 | - **Scan** - frequency of FTP scan (in seconds) 127 | - **Archive** - delete previously downloaded old files. Number specifies how many days you want to keep history. 128 | 129 | -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Client/Predicates/NewFilesPredicateTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NUnit.Framework; 3 | using Wikiled.YiScanner.Client.Predicates; 4 | 5 | namespace Wikiled.YiScanner.Tests.Client.Predicates 6 | { 7 | [TestFixture] 8 | public class NewFilesPredicateTests 9 | { 10 | private NewFilesPredicate instance; 11 | 12 | [SetUp] 13 | public void Setup() 14 | { 15 | instance = CreateNewFilesPredicate(); 16 | } 17 | 18 | [Test] 19 | public void CanDownload() 20 | { 21 | var result = instance.CanDownload(null, "Test.file", DateTime.Now); 22 | Assert.IsFalse(result); 23 | result = instance.CanDownload(DateTime.Now, "Test.file", DateTime.Now); 24 | Assert.IsFalse(result); 25 | result = instance.CanDownload(null, "Test.file", DateTime.Now); 26 | Assert.IsFalse(result); 27 | 28 | result = instance.CanDownload(DateTime.Now, "Test2.file", DateTime.Now); 29 | Assert.IsTrue(result); 30 | } 31 | 32 | private NewFilesPredicate CreateNewFilesPredicate() 33 | { 34 | return new NewFilesPredicate(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Client/VideoHeaderExtensionsTests.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using NUnit.Framework; 3 | using Wikiled.YiScanner.Client; 4 | using Wikiled.YiScanner.Monitoring.Source; 5 | 6 | namespace Wikiled.YiScanner.Tests.Client 7 | { 8 | [TestFixture] 9 | public class VideoHeaderExtensionsTests 10 | { 11 | [TestCase("file", @"c:\out\Camera\file")] 12 | [TestCase(@"c:\file", @"c:\out\Camera\file")] 13 | [TestCase(@"c:\Dir\file", @"c:\out\Camera\Dir\file")] 14 | [TestCase(@"c:\Dir2\Dir\file", @"c:\out\Camera\Dir\file")] 15 | public void GetPath(string fileName, string expected) 16 | { 17 | VideoHeader header = new VideoHeader(new Host("Camera", IPAddress.Any), fileName); 18 | var result = header.GetPath(@"c:\out"); 19 | Assert.AreEqual(expected, result); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Client/VideoHeaderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using NUnit.Framework; 4 | using Wikiled.YiScanner.Client; 5 | using Wikiled.YiScanner.Monitoring.Source; 6 | 7 | namespace Wikiled.YiScanner.Tests.Client 8 | { 9 | [TestFixture] 10 | public class VideoHeaderTests 11 | { 12 | private Host cameraDescription; 13 | 14 | private VideoHeader instance; 15 | 16 | [SetUp] 17 | public void Setup() 18 | { 19 | cameraDescription = new Host("Test", IPAddress.None); 20 | instance = CreateVideoHeader(); 21 | } 22 | 23 | [Test] 24 | public void Construct() 25 | { 26 | Assert.Throws(() => new VideoHeader(null, "Test")); 27 | Assert.Throws(() => new VideoHeader(cameraDescription, null)); 28 | Assert.AreEqual(cameraDescription, instance.Camera); 29 | Assert.AreEqual("Test", instance.FileName); 30 | } 31 | 32 | private VideoHeader CreateVideoHeader() 33 | { 34 | return new VideoHeader(cameraDescription, "Test"); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Data/Test.txt: -------------------------------------------------------------------------------- 1 | Test File -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Data/centaur_1.mpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AndMu/YiScanner/6f4a22551c9c4fc63d08ea3b0d0061efac8ce195/src/Wikiled.YiScanner.Tests/Data/centaur_1.mpg -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/ChainedActionDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using NUnit.Framework; 7 | using Wikiled.YiScanner.Client; 8 | using Wikiled.YiScanner.Destinations; 9 | using Wikiled.YiScanner.Monitoring.Source; 10 | 11 | namespace Wikiled.YiScanner.Tests.Destinations 12 | { 13 | [TestFixture] 14 | public class ChainedActionDestinationTests 15 | { 16 | private Mock mockDestination; 17 | 18 | private Mock mockAction; 19 | 20 | private ChainedPriorActionDestination instance; 21 | 22 | private VideoHeader header; 23 | 24 | private Mock stream; 25 | 26 | [SetUp] 27 | public void SetUp() 28 | { 29 | header = new VideoHeader(new Host("Camera", IPAddress.Any), "test.mov"); 30 | mockDestination = new Mock(); 31 | mockAction = new Mock(); 32 | stream = new Mock(); 33 | instance = CreateChainedActionDestination(); 34 | } 35 | 36 | [Test] 37 | public void CheckArguments() 38 | { 39 | Assert.Throws(() => instance.IsDownloaded(null)); 40 | Assert.Throws(() => instance.ResolveName(null)); 41 | Assert.ThrowsAsync(() => instance.Transfer(header, null)); 42 | Assert.ThrowsAsync(() => instance.Transfer(null, stream.Object)); 43 | } 44 | 45 | [Test] 46 | public void ResolveName() 47 | { 48 | mockDestination.Setup(item => item.ResolveName(It.IsAny())).Returns("test"); 49 | var name = instance.ResolveName(header); 50 | Assert.AreEqual("test", name); 51 | } 52 | 53 | [Test] 54 | public void IsDownloaded() 55 | { 56 | mockDestination.Setup(item => item.IsDownloaded(It.IsAny())).Returns(true); 57 | var result = instance.IsDownloaded(header); 58 | Assert.IsTrue(result); 59 | } 60 | 61 | [Test] 62 | public async Task Transfer() 63 | { 64 | mockAction.Setup(item => item.BeforeTransfer(header, stream.Object)) 65 | .Returns(Task.FromResult((header, stream.Object))); 66 | mockDestination.Setup(item => item.ResolveName(header)).Returns("Test"); 67 | mockDestination.Setup(item => item.Transfer(It.IsAny(), stream.Object)).Returns(Task.CompletedTask); 68 | await instance.Transfer(header, stream.Object).ConfigureAwait(false); 69 | mockAction.Verify(item => item.BeforeTransfer(header, stream.Object), Times.Exactly(1)); 70 | } 71 | 72 | [Test] 73 | public void Construct() 74 | { 75 | Assert.Throws(() => new ChainedPriorActionDestination(null, mockAction.Object)); 76 | Assert.Throws(() => new ChainedPriorActionDestination(mockDestination.Object, null)); 77 | } 78 | 79 | private ChainedPriorActionDestination CreateChainedActionDestination() 80 | { 81 | return new ChainedPriorActionDestination(mockDestination.Object, mockAction.Object); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/ChainedPostActionDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using Moq; 6 | using NUnit.Framework; 7 | using Wikiled.YiScanner.Client; 8 | using Wikiled.YiScanner.Destinations; 9 | using Wikiled.YiScanner.Monitoring.Source; 10 | 11 | namespace Wikiled.YiScanner.Tests.Destinations 12 | { 13 | [TestFixture] 14 | public class ChainedPostActionDestinationTests 15 | { 16 | private Mock mockDestination; 17 | 18 | private Mock mockPostAction; 19 | 20 | private VideoHeader header; 21 | 22 | private Mock stream; 23 | 24 | private ChainedPostActionDestination instance; 25 | 26 | [SetUp] 27 | public void SetUp() 28 | { 29 | header = new VideoHeader(new Host("Camera", IPAddress.Any), "test.mov"); 30 | stream = new Mock(); 31 | mockDestination = new Mock(); 32 | mockPostAction = new Mock(); 33 | instance = CreateChainedPostActionDestination(); 34 | } 35 | [Test] 36 | public void ResolveName() 37 | { 38 | mockDestination.Setup(item => item.ResolveName(It.IsAny())).Returns("test"); 39 | var name = instance.ResolveName(header); 40 | Assert.AreEqual("test", name); 41 | } 42 | 43 | [Test] 44 | public void IsDownloaded() 45 | { 46 | mockDestination.Setup(item => item.IsDownloaded(It.IsAny())).Returns(true); 47 | var result = instance.IsDownloaded(header); 48 | Assert.IsTrue(result); 49 | } 50 | 51 | [Test] 52 | public async Task Transfer() 53 | { 54 | mockPostAction.Setup(item => item.AfterTransfer("Test")).Returns(Task.FromResult(true)); 55 | mockDestination.Setup(item => item.ResolveName(header)).Returns("Test"); 56 | mockDestination.Setup(item => item.Transfer(It.IsAny(), stream.Object)).Returns(Task.CompletedTask); 57 | await instance.Transfer(header, stream.Object).ConfigureAwait(false); 58 | } 59 | 60 | [Test] 61 | public void Construct() 62 | { 63 | Assert.Throws(() => new ChainedPostActionDestination(null, mockPostAction.Object)); 64 | Assert.Throws(() => new ChainedPostActionDestination(mockDestination.Object, null)); 65 | } 66 | 67 | private ChainedPostActionDestination CreateChainedPostActionDestination() 68 | { 69 | return new ChainedPostActionDestination( 70 | mockDestination.Object, 71 | mockPostAction.Object); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/CompressedDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Net; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Wikiled.YiScanner.Client; 6 | using Wikiled.YiScanner.Destinations; 7 | using Wikiled.YiScanner.Monitoring.Source; 8 | 9 | namespace Wikiled.YiScanner.Tests.Destinations 10 | { 11 | [TestFixture] 12 | public class CompressedDestinationTests 13 | { 14 | private IDestination destination; 15 | 16 | private IDestination instance; 17 | 18 | private string outFile; 19 | 20 | [SetUp] 21 | public void Setup() 22 | { 23 | var outPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Data"); 24 | destination = new FileDestination(outPath); 25 | outFile = Path.Combine(outPath, "camera", "test.zip"); 26 | instance = ChainedPriorActionDestination.CreateCompressed(destination); 27 | if (File.Exists(outFile)) 28 | { 29 | File.Delete(outFile); 30 | } 31 | } 32 | 33 | [Test] 34 | public async Task Transfer() 35 | { 36 | Assert.IsFalse(File.Exists(outFile)); 37 | VideoHeader header = new VideoHeader(new Host("Camera", IPAddress.Any), "test.txt"); 38 | using (StreamReader reader = new StreamReader(Path.Combine(TestContext.CurrentContext.TestDirectory, "Data", "Test.txt"))) 39 | { 40 | await instance.Transfer(header, reader.BaseStream).ConfigureAwait(false); 41 | } 42 | 43 | Assert.IsTrue(File.Exists(outFile)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/FileDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Wikiled.YiScanner.Client; 7 | using Wikiled.YiScanner.Destinations; 8 | using Wikiled.YiScanner.Monitoring.Source; 9 | 10 | namespace Wikiled.YiScanner.Tests.Destinations 11 | { 12 | [TestFixture] 13 | public class FileDestinationTests 14 | { 15 | private FileDestination instance; 16 | 17 | private string outPath; 18 | 19 | private VideoHeader header; 20 | 21 | private MemoryStream stream; 22 | 23 | [SetUp] 24 | public void SetUp() 25 | { 26 | header = new VideoHeader(new Host("Camera", IPAddress.Any), "test.mov"); 27 | outPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "Out"); 28 | if (Directory.Exists(outPath)) 29 | { 30 | Directory.Delete(outPath, true); 31 | } 32 | 33 | instance = CreateFileDestination(); 34 | stream = new MemoryStream(new byte[] { 1 }); 35 | } 36 | 37 | [TearDown] 38 | public void TestCleanup() 39 | { 40 | stream.Dispose(); 41 | } 42 | 43 | [Test] 44 | public void CheckArguments() 45 | { 46 | Assert.Throws(() => instance.IsDownloaded(null)); 47 | Assert.Throws(() => instance.ResolveName(null)); 48 | Assert.ThrowsAsync(() => instance.Transfer(header, null)); 49 | Assert.ThrowsAsync(() => instance.Transfer(null, stream)); 50 | } 51 | 52 | [Test] 53 | public async Task TestFunctionality() 54 | { 55 | var result = instance.IsDownloaded(header); 56 | Assert.IsFalse(result); 57 | await instance.Transfer(header, stream).ConfigureAwait(false); 58 | result = instance.IsDownloaded(header); 59 | Assert.IsTrue(result); 60 | var name = instance.ResolveName(header); 61 | result = File.Exists(name); 62 | Assert.IsTrue(result); 63 | } 64 | 65 | [Test] 66 | public void Construct() 67 | { 68 | Assert.Throws(() => new FileDestination(null)); 69 | } 70 | 71 | private FileDestination CreateFileDestination() 72 | { 73 | return new FileDestination(outPath); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/PictureFileDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using NUnit.Framework; 6 | using Wikiled.YiScanner.Client; 7 | using Wikiled.YiScanner.Destinations; 8 | using Wikiled.YiScanner.Monitoring.Source; 9 | 10 | namespace Wikiled.YiScanner.Tests.Destinations 11 | { 12 | [TestFixture] 13 | public class PictureFileDestinationTests 14 | { 15 | private string outPath; 16 | 17 | private PictureFileDestination instance; 18 | 19 | private VideoHeader header; 20 | 21 | private Stream stream; 22 | 23 | [SetUp] 24 | public void SetUp() 25 | { 26 | outPath = Path.Combine(TestContext.CurrentContext.TestDirectory, "out"); 27 | if(Directory.Exists(outPath)) 28 | { 29 | Directory.Delete(outPath, true); 30 | } 31 | 32 | header = new VideoHeader(new Host("Camera", IPAddress.Any), "test.mov"); 33 | instance = CreatePictureFileDestination(); 34 | stream = File.OpenRead(Path.Combine(TestContext.CurrentContext.TestDirectory, "data", "centaur_1.mpg")); 35 | } 36 | 37 | [TearDown] 38 | public void TestCleanup() 39 | { 40 | stream.Dispose(); 41 | } 42 | 43 | [Test] 44 | public void ResolveName() 45 | { 46 | var result = instance.ResolveName(header); 47 | Assert.AreEqual(Path.Combine(outPath, "Camera", "test.png"), result); 48 | } 49 | 50 | [Test] 51 | public async Task Transfer() 52 | { 53 | var result = instance.IsDownloaded(header); 54 | Assert.IsFalse(result); 55 | result = File.Exists(Path.Combine(outPath, "Camera", "test.png")); 56 | Assert.IsFalse(result); 57 | 58 | await instance.Transfer(header, stream).ConfigureAwait(false); 59 | 60 | result = File.Exists(Path.Combine(outPath, "Camera", "test.png")); 61 | Assert.IsTrue(result); 62 | result = instance.IsDownloaded(header); 63 | Assert.IsTrue(result); 64 | } 65 | 66 | [Test] 67 | public void Construct() 68 | { 69 | Assert.Throws(() => new PictureFileDestination(null)); 70 | } 71 | 72 | private PictureFileDestination CreatePictureFileDestination() 73 | { 74 | return new PictureFileDestination(outPath); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Destinations/TransformedDestinationTests.cs: -------------------------------------------------------------------------------- 1 | using Moq; 2 | using NUnit.Framework; 3 | using System; 4 | using System.IO; 5 | using System.Net; 6 | using System.Threading.Tasks; 7 | using Wikiled.YiScanner.Client; 8 | using Wikiled.YiScanner.Destinations; 9 | using Wikiled.YiScanner.Monitoring.Source; 10 | 11 | namespace Wikiled.YiScanner.Tests.Destinations 12 | { 13 | [TestFixture] 14 | public class TransformedDestinationTests 15 | { 16 | private Mock mockDestination; 17 | 18 | private Func renameFunc; 19 | 20 | private TransformedDestination instance; 21 | 22 | private VideoHeader header; 23 | 24 | private Mock stream; 25 | 26 | [SetUp] 27 | public void SetUp() 28 | { 29 | header = new VideoHeader(new Host("Test", IPAddress.Any), "test.mov"); 30 | mockDestination = new Mock(); 31 | renameFunc = name => "test"; 32 | stream = new Mock(); 33 | instance = CreateTransformedDestination(); 34 | } 35 | 36 | [Test] 37 | public void CheckArguments() 38 | { 39 | Assert.Throws(() => instance.IsDownloaded(null)); 40 | Assert.Throws(() => instance.ResolveName(null)); 41 | Assert.ThrowsAsync(() => instance.Transfer(header, null)); 42 | Assert.ThrowsAsync(() => instance.Transfer(null, stream.Object)); 43 | } 44 | 45 | [Test] 46 | public void ResolveName() 47 | { 48 | VideoHeader inpuParameter = null; 49 | mockDestination.Setup(item => item.ResolveName(It.IsAny())).Returns("test") 50 | .Callback( 51 | parameter => { inpuParameter = parameter; }); 52 | var name = instance.ResolveName(header); 53 | Assert.AreEqual("test", name); 54 | Assert.AreEqual("test", inpuParameter.FileName); 55 | } 56 | 57 | [Test] 58 | public void IsDownloaded() 59 | { 60 | VideoHeader inpuParameter = null; 61 | mockDestination.Setup(item => item.IsDownloaded(It.IsAny())).Returns(true) 62 | .Callback( 63 | parameter => { inpuParameter = parameter; }); 64 | var result = instance.IsDownloaded(header); 65 | Assert.IsTrue(result); 66 | Assert.AreEqual("test", inpuParameter.FileName); 67 | } 68 | 69 | [Test] 70 | public async Task Transfer() 71 | { 72 | VideoHeader inpuParameter = null; 73 | mockDestination.Setup(item => item.Transfer(It.IsAny(), stream.Object)).Returns(Task.CompletedTask) 74 | .Callback( 75 | (parameter, inputStream) => { inpuParameter = parameter; }); 76 | await instance.Transfer(header, stream.Object); 77 | Assert.AreEqual("test", inpuParameter.FileName); 78 | } 79 | 80 | [Test] 81 | public void ChangeExtension() 82 | { 83 | VideoHeader inpuParameter = null; 84 | mockDestination.Setup(item => item.ResolveName(It.IsAny())).Returns("test") 85 | .Callback( 86 | parameter => { inpuParameter = parameter; }); 87 | var result = TransformedDestination.ChangeExtension(mockDestination.Object, "png"); 88 | result.ResolveName(header); 89 | Assert.AreEqual("test.png", inpuParameter.FileName); 90 | } 91 | 92 | [Test] 93 | public void Construct() 94 | { 95 | Assert.Throws(() => new TransformedDestination(null, renameFunc)); 96 | Assert.Throws(() => new TransformedDestination(mockDestination.Object, null)); 97 | } 98 | 99 | private TransformedDestination CreateTransformedDestination() 100 | { 101 | return new TransformedDestination(mockDestination.Object, renameFunc); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Monitoring/MonitoringInstanceTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Threading.Tasks; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.Reactive.Testing; 7 | using Moq; 8 | using NUnit.Framework; 9 | using Wikiled.YiScanner.Client; 10 | using Wikiled.YiScanner.Client.Archive; 11 | using Wikiled.YiScanner.Downloader; 12 | using Wikiled.YiScanner.Monitoring; 13 | using Wikiled.YiScanner.Monitoring.Config; 14 | using Wikiled.YiScanner.Monitoring.Source; 15 | 16 | namespace Wikiled.YiScanner.Tests.Monitoring 17 | { 18 | [TestFixture] 19 | public class MonitoringInstanceTests : ReactiveTest 20 | { 21 | private MonitoringConfig monitoringConfig; 22 | 23 | private Mock mockDestinationFactory; 24 | 25 | private Mock ftpDownloader; 26 | 27 | private Mock archiving; 28 | 29 | private MonitoringInstance instance; 30 | 31 | private TestScheduler scheduler; 32 | 33 | [SetUp] 34 | public void SetUp() 35 | { 36 | scheduler = new TestScheduler(); 37 | monitoringConfig = new MonitoringConfig(); 38 | monitoringConfig.Scan = 1; 39 | monitoringConfig.Known = new PredefinedCameraConfig(); 40 | monitoringConfig.Output = new OutputConfig(); 41 | archiving = new Mock(); 42 | mockDestinationFactory = new Mock(); 43 | ftpDownloader = new Mock(); 44 | 45 | mockDestinationFactory.Setup(item => item.GetSources(It.IsAny())) 46 | .Returns(new[] { ftpDownloader.Object }); 47 | instance = CreateMonitoringInstance(); 48 | } 49 | 50 | [Test] 51 | public void Download() 52 | { 53 | instance.Start(); 54 | scheduler.AdvanceBy(TimeSpan.FromSeconds(11).Ticks); 55 | ftpDownloader.Verify(item => item.Download(It.IsAny()), Times.Exactly(10)); 56 | } 57 | 58 | [Test] 59 | public void DownloadSlow() 60 | { 61 | ftpDownloader.Setup(item => item.Download(It.IsAny())) 62 | .Returns( 63 | () => 64 | Observable.Return(DateTime.Now) 65 | .Delay(TimeSpan.FromSeconds(3), scheduler) 66 | .ToTask()); 67 | instance.Start(); 68 | scheduler.AdvanceBy(TimeSpan.FromSeconds(10).Ticks); 69 | ftpDownloader.Verify(item => item.Download(It.IsAny()), Times.Exactly(3)); 70 | } 71 | 72 | [Test] 73 | public void Archive() 74 | { 75 | monitoringConfig.Archive = 2; 76 | monitoringConfig.Scan = int.MaxValue; 77 | archiving.Setup(item => item.Archive(It.IsAny(), It.IsAny())).Returns(() => Observable.Return(true).Delay(TimeSpan.FromSeconds(5), scheduler).ToTask()); 78 | instance.Start(); 79 | scheduler.AdvanceBy(TimeSpan.FromDays(3).Ticks); 80 | archiving.Verify(item => item.Archive(It.IsAny(), It.IsAny()), Times.Exactly(2)); 81 | } 82 | 83 | [Test] 84 | public void Construct() 85 | { 86 | Assert.Throws(() => new MonitoringInstance( 87 | null, 88 | monitoringConfig, 89 | mockDestinationFactory.Object, 90 | archiving.Object)); 91 | 92 | Assert.Throws(() => new MonitoringInstance( 93 | scheduler, 94 | null, 95 | mockDestinationFactory.Object, 96 | archiving.Object)); 97 | 98 | Assert.Throws(() => new MonitoringInstance( 99 | scheduler, 100 | monitoringConfig, 101 | null, 102 | archiving.Object)); 103 | 104 | Assert.Throws(() => new MonitoringInstance( 105 | scheduler, 106 | monitoringConfig, 107 | mockDestinationFactory.Object, 108 | null)); 109 | } 110 | 111 | private MonitoringInstance CreateMonitoringInstance() 112 | { 113 | return new MonitoringInstance( 114 | scheduler, 115 | monitoringConfig, 116 | mockDestinationFactory.Object, 117 | archiving.Object); 118 | } 119 | } 120 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Monitoring/Source/SourceFactoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Net; 4 | using Moq; 5 | using NUnit.Framework; 6 | using Wikiled.YiScanner.Client.Predicates; 7 | using Wikiled.YiScanner.Monitoring.Config; 8 | using Wikiled.YiScanner.Monitoring.Source; 9 | 10 | namespace Wikiled.YiScanner.Tests.Monitoring.Source 11 | { 12 | [TestFixture] 13 | public class SourceFactoryTests 14 | { 15 | private MonitoringConfig scanConfig; 16 | 17 | private Mock mockPredicate; 18 | 19 | private SourceFactory instance; 20 | 21 | private StaticHostManager manager; 22 | 23 | [SetUp] 24 | public void SetUp() 25 | { 26 | scanConfig = new MonitoringConfig(); 27 | scanConfig.YiFtp = new FtpConfig(); 28 | scanConfig.YiFtp.FileMask = "Test"; 29 | scanConfig.Output = new OutputConfig(); 30 | scanConfig.Output.Out = "Out"; 31 | mockPredicate = new Mock(); 32 | scanConfig.Known = new PredefinedCameraConfig(); 33 | manager = new StaticHostManager(scanConfig.Known); 34 | instance = CreateFactory(); 35 | } 36 | 37 | [Test] 38 | public void GetSources() 39 | { 40 | var result = instance.GetSources(manager).ToArray(); 41 | Assert.AreEqual(0, result.Length); 42 | 43 | scanConfig.Known.Cameras = "Test"; 44 | result = instance.GetSources(manager).ToArray(); 45 | Assert.AreEqual(0, result.Length); 46 | 47 | scanConfig.Known.Hosts = IPAddress.Any.ToString(); 48 | result = instance.GetSources(manager).ToArray(); 49 | Assert.AreEqual(1, result.Length); 50 | } 51 | 52 | [Test] 53 | public void Construct() 54 | { 55 | Assert.Throws(() => new SourceFactory(null, mockPredicate.Object)); 56 | Assert.Throws(() => new SourceFactory(scanConfig, null)); 57 | } 58 | 59 | private SourceFactory CreateFactory() 60 | { 61 | return new SourceFactory(scanConfig, mockPredicate.Object); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Network/NetworkScannerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Concurrency; 2 | using System.Reactive.Linq; 3 | using System.Threading.Tasks; 4 | using NUnit.Framework; 5 | using Wikiled.YiScanner.Network; 6 | 7 | namespace Wikiled.YiScanner.Tests.Network 8 | { 9 | [TestFixture] 10 | public class NetworkScannerTests 11 | { 12 | private NetworkScanner instance; 13 | 14 | [SetUp] 15 | public void SetUp() 16 | { 17 | instance = CreateNetworkScanner(); 18 | } 19 | 20 | [Test] 21 | public async Task FindAddress() 22 | { 23 | var result = await instance.FindAddresses("192.168.0.0/255.255.255.0", 21).ToArray(); 24 | } 25 | 26 | private NetworkScanner CreateNetworkScanner() 27 | { 28 | return new NetworkScanner(TaskPoolScheduler.Default); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/Wikiled.YiScanner.Tests/Wikiled.YiScanner.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NET462 5 | win7-x86 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | PreserveNewest 39 | 40 | 41 | PreserveNewest 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/YiScanner.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2010 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wikiled.YiScanner.Tests", "Wikiled.YiScanner.Tests\Wikiled.YiScanner.Tests.csproj", "{2B74D4F5-00E8-4B28-8D18-45BB2B519396}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wikiled.YiScanner", "YiScanner\Wikiled.YiScanner.csproj", "{D00432A5-8188-46D7-BC29-F6F12CCA9765}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {2B74D4F5-00E8-4B28-8D18-45BB2B519396}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {2B74D4F5-00E8-4B28-8D18-45BB2B519396}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {2B74D4F5-00E8-4B28-8D18-45BB2B519396}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {2B74D4F5-00E8-4B28-8D18-45BB2B519396}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {D00432A5-8188-46D7-BC29-F6F12CCA9765}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D00432A5-8188-46D7-BC29-F6F12CCA9765}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D00432A5-8188-46D7-BC29-F6F12CCA9765}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D00432A5-8188-46D7-BC29-F6F12CCA9765}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {957B68B7-5DEF-4A2F-A399-5B85139E9AAA} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/YiScanner/Client/Archive/DeleteArchiving.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using NLog; 6 | 7 | namespace Wikiled.YiScanner.Client.Archive 8 | { 9 | public class DeleteArchiving : IDeleteArchiving 10 | { 11 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 12 | 13 | public Task Archive(string destination, TimeSpan time) 14 | { 15 | return Task.Run(() => ArchiveInternal(destination, time)); 16 | } 17 | 18 | private void ArchiveInternal(string destination, TimeSpan time) 19 | { 20 | if (!Directory.Exists(destination)) 21 | { 22 | log.Warn("Output doesn't exist: {0}", destination); 23 | return; 24 | } 25 | 26 | var files = Directory.EnumerateFiles(destination, "*", SearchOption.AllDirectories); 27 | DateTime cutOff = DateTime.Today.Subtract(time); 28 | Parallel.ForEach( 29 | files, 30 | file => 31 | { 32 | try 33 | { 34 | var info = new FileInfo(file); 35 | if (info.CreationTime < cutOff) 36 | { 37 | log.Debug("Deleting: {0}", file); 38 | File.Delete(file); 39 | } 40 | else 41 | { 42 | log.Debug("Keeping: {0}", file); 43 | } 44 | } 45 | catch(Exception ex) 46 | { 47 | log.Error(ex); 48 | } 49 | }); 50 | 51 | try 52 | { 53 | var directories = Directory.EnumerateDirectories(destination); 54 | foreach (var directory in directories) 55 | { 56 | if (!Directory.EnumerateFileSystemEntries(directory).Any()) 57 | { 58 | Directory.Delete(directory); 59 | } 60 | } 61 | } 62 | catch (Exception ex) 63 | { 64 | log.Error(ex); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/YiScanner/Client/Archive/IDeleteArchiving.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Wikiled.YiScanner.Client.Archive 5 | { 6 | public interface IDeleteArchiving 7 | { 8 | Task Archive(string destination, TimeSpan time); 9 | } 10 | } -------------------------------------------------------------------------------- /src/YiScanner/Client/FileMask.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | namespace Wikiled.YiScanner.Client 4 | { 5 | public static class FileMask 6 | { 7 | public static Regex GenerateFitMask(string fileMask) 8 | { 9 | string pattern = 10 | '^' + 11 | Regex.Escape(fileMask.Replace(".", "__DOT__") 12 | .Replace("*", "__STAR__") 13 | .Replace("?", "__QM__")) 14 | .Replace("__DOT__", "[.]") 15 | .Replace("__STAR__", ".*") 16 | .Replace("__QM__", ".") 17 | + '$'; 18 | return new Regex(pattern, RegexOptions.IgnoreCase); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/YiScanner/Client/Predicates/IPredicate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Wikiled.YiScanner.Client.Predicates 4 | { 5 | public interface IPredicate 6 | { 7 | bool CanDownload(DateTime? lastScan, string file, DateTime modified); 8 | 9 | void Downloaded(string file); 10 | } 11 | } -------------------------------------------------------------------------------- /src/YiScanner/Client/Predicates/NewFilesPredicate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using NLog; 4 | 5 | namespace Wikiled.YiScanner.Client.Predicates 6 | { 7 | public class NewFilesPredicate : IPredicate 8 | { 9 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 10 | 11 | private readonly ConcurrentDictionary table = new ConcurrentDictionary(); 12 | 13 | public NewFilesPredicate() 14 | { 15 | log.Debug("Constructing"); 16 | } 17 | 18 | public bool CanDownload(DateTime? lastScan, string file, DateTime modified) 19 | { 20 | if (table.ContainsKey(file)) 21 | { 22 | return false; 23 | } 24 | 25 | if (lastScan == null) 26 | { 27 | table[file] = true; 28 | } 29 | 30 | var result = lastScan != null; 31 | log.Debug("Can download ({1}): {0}", file, result); 32 | return result; 33 | } 34 | 35 | public void Downloaded(string file) 36 | { 37 | table[file] = true; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/YiScanner/Client/Predicates/NullPredicate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NLog; 3 | 4 | namespace Wikiled.YiScanner.Client.Predicates 5 | { 6 | public class NullPredicate : IPredicate 7 | { 8 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 9 | 10 | public NullPredicate() 11 | { 12 | log.Debug("Constructing"); 13 | } 14 | 15 | public bool CanDownload(DateTime? lastScan, string file, DateTime modified) 16 | { 17 | return true; 18 | } 19 | 20 | public void Downloaded(string file) 21 | { 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/YiScanner/Client/VideoHeader.cs: -------------------------------------------------------------------------------- 1 | using Wikiled.Common.Arguments; 2 | using Wikiled.YiScanner.Monitoring.Source; 3 | 4 | namespace Wikiled.YiScanner.Client 5 | { 6 | public class VideoHeader 7 | { 8 | public VideoHeader(Host camera, string fileName) 9 | { 10 | Guard.NotNull(() => camera, camera); 11 | Guard.NotNullOrEmpty(() => fileName, fileName); 12 | Camera = camera; 13 | FileName = fileName; 14 | } 15 | 16 | public Host Camera { get; } 17 | 18 | public string FileName { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/YiScanner/Client/VideoHeaderExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Wikiled.Common.Arguments; 3 | using Wikiled.Common.Extensions; 4 | 5 | namespace Wikiled.YiScanner.Client 6 | { 7 | public static class VideoHeaderExtensions 8 | { 9 | public static string GetPath(this VideoHeader header, string destination) 10 | { 11 | Guard.NotNull(() => header, header); 12 | Guard.NotNullOrEmpty(() => destination, destination); 13 | var fileName = Path.GetFileName(header.FileName); 14 | var dirName = Path.GetDirectoryName(header.FileName); 15 | dirName = Path.GetFileName(dirName); 16 | var dirDestination = Path.Combine(destination, header.Camera.Name, dirName); 17 | dirDestination.EnsureDirectoryExistence(); 18 | var fileDestination = Path.Combine(dirDestination, fileName); 19 | return fileDestination; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/YiScanner/Commands/BaseCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using NLog; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.Console.Arguments; 6 | using Wikiled.YiScanner.Client.Predicates; 7 | using Wikiled.YiScanner.Monitoring.Config; 8 | using Wikiled.YiScanner.Monitoring.Source; 9 | 10 | namespace Wikiled.YiScanner.Commands 11 | { 12 | public abstract class BaseCommand : Command 13 | { 14 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 15 | 16 | protected BaseCommand(MonitoringConfig config) 17 | { 18 | Guard.NotNull(() => config, config); 19 | Config = config; 20 | } 21 | 22 | [Description("Archive video after days")] 23 | public int? Archive { get; set; } 24 | 25 | [Description("Auto discover cameras")] 26 | public bool? AutoDiscover { get; set; } 27 | 28 | [Required] 29 | [Description("List of camera names")] 30 | public string Cameras { get; set; } 31 | 32 | [Description("Compress video files")] 33 | public bool? Compress { get; set; } 34 | 35 | public MonitoringConfig Config { get; } 36 | 37 | [Required] 38 | [Description("List of camera hosts")] 39 | public string Hosts { get; set; } 40 | 41 | [Description("Do you want to save it as image")] 42 | public bool? Images { get; set; } 43 | 44 | [Description("Discovery network mask")] 45 | public string NetworkMask { get; set; } 46 | 47 | [Required] 48 | [Description("File destination")] 49 | public string Out { get; set; } 50 | 51 | public override void Execute() 52 | { 53 | log.Info("Starting camera download..."); 54 | SetConfig(); 55 | var factory = new SourceFactory(Config, ConstructPredicate()); 56 | ProcessFtp(factory); 57 | } 58 | 59 | protected abstract IPredicate ConstructPredicate(); 60 | 61 | protected abstract void ProcessFtp(ISourceFactory downloaders); 62 | 63 | private void SetConfig() 64 | { 65 | log.Info("Initializing config"); 66 | Config.Archive = Archive; 67 | if (Config.AutoDiscovery == null) 68 | { 69 | Config.AutoDiscovery = new AutoDiscoveryConfig(); 70 | } 71 | 72 | Config.AutoDiscovery.On = AutoDiscover; 73 | Config.AutoDiscovery.NetworkMask = NetworkMask; 74 | 75 | if (Config.Known == null) 76 | { 77 | Config.Known = new PredefinedCameraConfig(); 78 | } 79 | 80 | Config.Known.Cameras = Cameras; 81 | Config.Known.Hosts = Hosts; 82 | 83 | if (Config.Output == null) 84 | { 85 | Config.Output = new OutputConfig(); 86 | } 87 | 88 | Config.Output.Compress = Compress == true; 89 | Config.Output.Out = Out; 90 | Config.Output.Images = Images == true; 91 | 92 | // ignore FTP Server 93 | Config.Server = null; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/YiScanner/Commands/DownloadCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Linq; 4 | using System.Reactive.Concurrency; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using NLog; 8 | using Wikiled.YiScanner.Client.Archive; 9 | using Wikiled.YiScanner.Client.Predicates; 10 | using Wikiled.YiScanner.Monitoring.Config; 11 | using Wikiled.YiScanner.Monitoring.Source; 12 | using Wikiled.YiScanner.Network; 13 | 14 | namespace Wikiled.YiScanner.Commands 15 | { 16 | /// 17 | /// Download -Cameras=1080i -Hosts=192.168.0.202 -Compress -Out=c:\out -Scan=10 [-Archive=2] 18 | /// 19 | [Description("Download video from camera")] 20 | public class DownloadCommand : BaseCommand 21 | { 22 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 23 | 24 | public DownloadCommand(MonitoringConfig config) 25 | : base(config) 26 | { 27 | } 28 | 29 | protected override IPredicate ConstructPredicate() 30 | { 31 | return new NullPredicate(); 32 | } 33 | 34 | protected override void ProcessFtp(ISourceFactory factory) 35 | { 36 | using (var hostManager = AutoDiscover == true ? 37 | new DynamicHostManager(Config, new NetworkScanner(TaskPoolScheduler.Default), TaskPoolScheduler.Default) 38 | : (IHostManager)new StaticHostManager(Config.Known)) 39 | { 40 | CancellationToken token = CancellationToken.None; 41 | if (Config.TimeOut.HasValue) 42 | { 43 | CancellationTokenSource tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(Config.TimeOut.Value)); 44 | token = tokenSource.Token; 45 | } 46 | 47 | var downloaders = factory.GetSources(hostManager); 48 | var tasks = downloaders.Select(ftpDownloader => ftpDownloader.Download(token)); 49 | 50 | var archiving = new DeleteArchiving(); 51 | if (Archive.HasValue) 52 | { 53 | log.Info("Archiving..."); 54 | archiving.Archive(Out, TimeSpan.FromDays(Archive.Value)); 55 | } 56 | 57 | Task.WhenAll(tasks).Wait(token); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/YiScanner/Commands/MonitorCommand.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Reactive.Concurrency; 4 | using NLog; 5 | using Wikiled.YiScanner.Client.Archive; 6 | using Wikiled.YiScanner.Client.Predicates; 7 | using Wikiled.YiScanner.Monitoring; 8 | using Wikiled.YiScanner.Monitoring.Config; 9 | using Wikiled.YiScanner.Monitoring.Source; 10 | 11 | namespace Wikiled.YiScanner.Commands 12 | { 13 | /// 14 | /// Monitor -Cameras=1080i -Hosts=192.168.0.202 [-Compress] -Out=c:\out -Scan=10 [-Archive=2] 15 | /// 16 | [Description("Monitor new video from cameras")] 17 | public class MonitorCommand : BaseCommand 18 | { 19 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 20 | 21 | public MonitorCommand(MonitoringConfig config) 22 | : base(config) 23 | { 24 | } 25 | 26 | [Required] 27 | [Description("Scan interval in second")] 28 | public int Scan { get; set; } 29 | 30 | protected override IPredicate ConstructPredicate() 31 | { 32 | return new NewFilesPredicate(); 33 | } 34 | 35 | protected override void ProcessFtp(ISourceFactory downloaders) 36 | { 37 | var instance = new MonitoringInstance(TaskPoolScheduler.Default, Config, downloaders, new DeleteArchiving()); 38 | if (instance.Start()) 39 | { 40 | log.Info("Press enter to stop monitoring..."); 41 | System.Console.ReadLine(); 42 | instance.Stop(); 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ActionConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Destinations 2 | { 3 | public class ActionConfig 4 | { 5 | public ActionType Type { get; set; } 6 | 7 | public string Cmd { get; set; } 8 | 9 | public object Payload { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ActionType.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Converters; 3 | 4 | namespace Wikiled.YiScanner.Destinations 5 | { 6 | [JsonConverter(typeof(StringEnumConverter))] 7 | public enum ActionType 8 | { 9 | Execute, 10 | Rest 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ChainedPostActionDestination.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.YiScanner.Client; 6 | 7 | namespace Wikiled.YiScanner.Destinations 8 | { 9 | public class ChainedPostActionDestination : ChainedPriorActionDestinationBase 10 | { 11 | private readonly IDestination next; 12 | 13 | private readonly IPostAction postAction; 14 | 15 | public ChainedPostActionDestination(IDestination next, IPostAction postAction) 16 | : base(next) 17 | { 18 | Guard.NotNull(() => next, next); 19 | Guard.NotNull(() => postAction, postAction); 20 | this.next = next; 21 | this.postAction = postAction; 22 | } 23 | 24 | public static ChainedPostActionDestination CreateAction(IDestination next, ActionConfig config) 25 | { 26 | Guard.NotNull(() => config, config); 27 | if (config.Type == ActionType.Execute) 28 | { 29 | return new ChainedPostActionDestination(next, new ExecutePostAction(config)); 30 | } 31 | 32 | throw new NotImplementedException(); 33 | } 34 | 35 | public override async Task Transfer(VideoHeader header, Stream source) 36 | { 37 | Guard.NotNull(() => header, header); 38 | Guard.NotNull(() => source, source); 39 | var transfer = next.Transfer(header, source); 40 | var name = next.ResolveName(header); 41 | await transfer.ConfigureAwait(false); 42 | await postAction.AfterTransfer(name).ConfigureAwait(false); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ChainedPriorActionDestination.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Wikiled.Common.Arguments; 4 | using Wikiled.YiScanner.Client; 5 | 6 | namespace Wikiled.YiScanner.Destinations 7 | { 8 | public class ChainedPriorActionDestination : ChainedPriorActionDestinationBase 9 | { 10 | private readonly IPriorAction priorAction; 11 | 12 | private readonly IDestination next; 13 | 14 | public ChainedPriorActionDestination(IDestination next, IPriorAction priorAction) 15 | : base(next) 16 | { 17 | Guard.NotNull(() => next, next); 18 | Guard.NotNull(() => priorAction, priorAction); 19 | this.next = next; 20 | this.priorAction = priorAction; 21 | } 22 | 23 | public static IDestination CreateCompressed(IDestination next) 24 | { 25 | return TransformedDestination.ChangeExtension(new ChainedPriorActionDestination(next, new CompressPriorAction()), "zip"); 26 | } 27 | 28 | public override async Task Transfer(VideoHeader header, Stream source) 29 | { 30 | Guard.NotNull(() => header, header); 31 | Guard.NotNull(() => source, source); 32 | var result = await priorAction.BeforeTransfer(header, source).ConfigureAwait(false); 33 | using (result.source) 34 | { 35 | await next.Transfer(result.header, result.source).ConfigureAwait(false); 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ChainedPriorActionDestinationBase.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Wikiled.Common.Arguments; 4 | using Wikiled.YiScanner.Client; 5 | 6 | namespace Wikiled.YiScanner.Destinations 7 | { 8 | public abstract class ChainedPriorActionDestinationBase : IDestination 9 | { 10 | private readonly IDestination next; 11 | 12 | protected ChainedPriorActionDestinationBase(IDestination next) 13 | { 14 | Guard.NotNull(() => next, next); 15 | this.next = next; 16 | } 17 | 18 | public abstract Task Transfer(VideoHeader header, Stream source); 19 | 20 | public bool IsDownloaded(VideoHeader header) 21 | { 22 | Guard.NotNull(() => header, header); 23 | return next.IsDownloaded(header); 24 | } 25 | 26 | public string ResolveName(VideoHeader header) 27 | { 28 | Guard.NotNull(() => header, header); 29 | return next.ResolveName(header); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/YiScanner/Destinations/CompressPriorAction.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using System.Threading.Tasks; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.YiScanner.Client; 6 | 7 | namespace Wikiled.YiScanner.Destinations 8 | { 9 | public class CompressPriorAction : IPriorAction 10 | { 11 | public Task<(VideoHeader header, Stream source)> BeforeTransfer(VideoHeader header, Stream source) 12 | { 13 | Guard.NotNull(() => header, header); 14 | Guard.NotNull(() => source, source); 15 | var memory = new MemoryStream(); 16 | ZipArchive archive = new ZipArchive(memory, ZipArchiveMode.Create, true); 17 | ZipArchiveEntry readmeEntry = archive.CreateEntry(Path.GetFileName(header.FileName)); 18 | 19 | using (Stream entryStream = readmeEntry.Open()) 20 | { 21 | source.CopyTo(entryStream); 22 | } 23 | 24 | memory.Position = 0; 25 | return Task.FromResult((header, (Stream)memory)); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/ExecutePostAction.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using NLog; 5 | using Wikiled.Common.Arguments; 6 | using Wikiled.Common.Extensions; 7 | 8 | namespace Wikiled.YiScanner.Destinations 9 | { 10 | public class ExecutePostAction : IPostAction 11 | { 12 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 13 | 14 | private readonly ActionConfig config; 15 | 16 | public ExecutePostAction(ActionConfig config) 17 | { 18 | Guard.NotNull(() => config, config); 19 | this.config = config; 20 | } 21 | 22 | public Task AfterTransfer(string fileName) 23 | { 24 | if (string.IsNullOrEmpty(fileName)) 25 | { 26 | log.Warn("Can't process file: {0}", fileName); 27 | return Task.FromResult(false); 28 | } 29 | 30 | ProcessStartInfo startInfo = new ProcessStartInfo(); 31 | var path = config.Cmd.Replace("%1", fileName); 32 | log.Debug("Executing: {0}", fileName); 33 | var blocks = path.Split(' '); 34 | startInfo.FileName = blocks[0]; 35 | startInfo.Arguments = blocks.Skip(1).AccumulateItems(" "); 36 | startInfo.CreateNoWindow = true; 37 | startInfo.ErrorDialog = false; 38 | startInfo.UseShellExecute = true; 39 | startInfo.WindowStyle = ProcessWindowStyle.Hidden; 40 | 41 | var process = new Process(); 42 | process.StartInfo = startInfo; 43 | var result = process.Start(); 44 | return Task.FromResult(result); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/FileDestination.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Wikiled.Common.Arguments; 4 | using Wikiled.YiScanner.Client; 5 | 6 | namespace Wikiled.YiScanner.Destinations 7 | { 8 | public class FileDestination : IDestination 9 | { 10 | private readonly string destination; 11 | 12 | public FileDestination(string destination) 13 | { 14 | Guard.NotNullOrEmpty(() => destination, destination); 15 | this.destination = destination; 16 | } 17 | 18 | public bool IsDownloaded(VideoHeader header) 19 | { 20 | Guard.NotNull(() => header, header); 21 | var fileDestination = header.GetPath(destination); 22 | return File.Exists(fileDestination); 23 | } 24 | 25 | public string ResolveName(VideoHeader header) 26 | { 27 | Guard.NotNull(() => header, header); 28 | return header.GetPath(destination); 29 | } 30 | 31 | public async Task Transfer(VideoHeader header, Stream source) 32 | { 33 | Guard.NotNull(() => header, header); 34 | Guard.NotNull(() => source, source); 35 | var fileDestination = ResolveName(header); 36 | using (StreamWriter write = new StreamWriter(fileDestination)) 37 | { 38 | await source.CopyToAsync(write.BaseStream).ConfigureAwait(false); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/IDestination.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Wikiled.YiScanner.Client; 4 | 5 | namespace Wikiled.YiScanner.Destinations 6 | { 7 | public interface IDestination 8 | { 9 | Task Transfer(VideoHeader header, Stream source); 10 | 11 | bool IsDownloaded(VideoHeader header); 12 | 13 | string ResolveName(VideoHeader header); 14 | } 15 | } -------------------------------------------------------------------------------- /src/YiScanner/Destinations/IPostAction.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Wikiled.YiScanner.Destinations 4 | { 5 | public interface IPostAction 6 | { 7 | Task AfterTransfer(string fileName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/IPriorAction.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using Wikiled.YiScanner.Client; 4 | 5 | namespace Wikiled.YiScanner.Destinations 6 | { 7 | public interface IPriorAction 8 | { 9 | Task<(VideoHeader header, Stream source)> BeforeTransfer(VideoHeader header, Stream source); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/PictureFileDestination.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Drawing.Imaging; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Accord.Video.FFMPEG; 6 | using Wikiled.Common.Arguments; 7 | using Wikiled.YiScanner.Client; 8 | 9 | namespace Wikiled.YiScanner.Destinations 10 | { 11 | public class PictureFileDestination : IDestination 12 | { 13 | private readonly string destination; 14 | 15 | public PictureFileDestination(string destination) 16 | { 17 | Guard.NotNullOrEmpty(() => destination, destination); 18 | this.destination = destination; 19 | } 20 | 21 | public bool IsDownloaded(VideoHeader header) 22 | { 23 | Guard.NotNull(() => header, header); 24 | return File.Exists(ResolveName(header)); 25 | } 26 | 27 | public string ResolveName(VideoHeader header) 28 | { 29 | var fileDestination = header.GetPath(destination); 30 | return Path.ChangeExtension(fileDestination, "png"); 31 | } 32 | 33 | public async Task Transfer(VideoHeader header, Stream source) 34 | { 35 | Guard.NotNull(() => header, header); 36 | Guard.NotNull(() => source, source); 37 | 38 | var temp = Path.GetTempFileName(); 39 | using (StreamWriter write = new StreamWriter(temp)) 40 | { 41 | await source.CopyToAsync(write.BaseStream).ConfigureAwait(false); 42 | } 43 | 44 | try 45 | { 46 | using (VideoFileReader reader = new VideoFileReader()) 47 | { 48 | reader.Open(temp); 49 | using (Bitmap videoFrame = reader.ReadVideoFrame()) 50 | { 51 | videoFrame.Save(ResolveName(header), ImageFormat.Png); 52 | } 53 | } 54 | } 55 | finally 56 | { 57 | File.Delete(temp); 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/YiScanner/Destinations/TransformedDestination.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Threading.Tasks; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.YiScanner.Client; 6 | 7 | namespace Wikiled.YiScanner.Destinations 8 | { 9 | public class TransformedDestination : IDestination 10 | { 11 | private readonly IDestination another; 12 | 13 | private readonly Func nameChange; 14 | 15 | public TransformedDestination(IDestination another, Func nameChange) 16 | { 17 | Guard.NotNull(() => another, another); 18 | Guard.NotNull(() => nameChange, nameChange); 19 | this.another = another; 20 | this.nameChange = nameChange; 21 | } 22 | 23 | public static TransformedDestination ChangeExtension(IDestination another, string nenExtension) 24 | { 25 | return new TransformedDestination(another, fileName => Path.ChangeExtension(fileName, nenExtension)); 26 | } 27 | 28 | public Task Transfer(VideoHeader header, Stream source) 29 | { 30 | Guard.NotNull(() => header, header); 31 | Guard.NotNull(() => source, source); 32 | return another.Transfer(ConstructHeader(header), source); 33 | } 34 | 35 | public bool IsDownloaded(VideoHeader header) 36 | { 37 | Guard.NotNull(() => header, header); 38 | return another.IsDownloaded(ConstructHeader(header)); 39 | } 40 | 41 | public string ResolveName(VideoHeader header) 42 | { 43 | Guard.NotNull(() => header, header); 44 | return another.ResolveName(ConstructHeader(header)); 45 | } 46 | 47 | private VideoHeader ConstructHeader(VideoHeader header) 48 | { 49 | return new VideoHeader(header.Camera, nameChange(header.FileName)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/YiScanner/Downloader/FileDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using NLog; 8 | using Wikiled.Common.Arguments; 9 | using Wikiled.YiScanner.Client; 10 | using Wikiled.YiScanner.Client.Predicates; 11 | using Wikiled.YiScanner.Destinations; 12 | using Wikiled.YiScanner.Monitoring.Config; 13 | using Wikiled.YiScanner.Monitoring.Source; 14 | 15 | namespace Wikiled.YiScanner.Downloader 16 | { 17 | public class FileDownloader : IDownloader 18 | { 19 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 20 | 21 | private readonly IDestination destination; 22 | 23 | private readonly ServerConfig config; 24 | 25 | private readonly IPredicate predicate; 26 | 27 | private DateTime? lastScanned; 28 | 29 | public FileDownloader(ServerConfig config, IDestination destination, IPredicate predicate) 30 | { 31 | Guard.NotNull(() => config, config); 32 | Guard.NotNull(() => destination, destination); 33 | Guard.NotNull(() => predicate, predicate); 34 | this.config = config; 35 | this.predicate = predicate; 36 | this.destination = destination; 37 | } 38 | 39 | public async Task Download(CancellationToken cancellation) 40 | { 41 | var path = Path.Combine(Environment.CurrentDirectory, config.Path); 42 | log.Info("Checking files: {0}", path); 43 | if (!Directory.Exists(path)) 44 | { 45 | log.Error("Directory not found: {0}", path); 46 | return DateTime.Now; 47 | } 48 | 49 | foreach (var file in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) 50 | { 51 | try 52 | { 53 | cancellation.ThrowIfCancellationRequested(); 54 | if (predicate.CanDownload(lastScanned, file, File.GetLastWriteTime(file))) 55 | { 56 | await ProcessFile(file).ConfigureAwait(false); 57 | } 58 | 59 | File.Delete(file); 60 | } 61 | catch (Exception ex) 62 | { 63 | log.Error(ex); 64 | } 65 | } 66 | 67 | try 68 | { 69 | var directories = Directory.EnumerateDirectories(path, "*", SearchOption.AllDirectories); 70 | foreach (var directory in directories) 71 | { 72 | cancellation.ThrowIfCancellationRequested(); 73 | if (!Directory.EnumerateFileSystemEntries(directory).Any()) 74 | { 75 | log.Info("Removing empty: {0}", directory); 76 | Directory.Delete(directory); 77 | } 78 | } 79 | } 80 | catch (Exception ex) 81 | { 82 | log.Error(ex); 83 | } 84 | 85 | log.Info("Completed: {0}", path); 86 | var now = DateTime.Now; 87 | lastScanned = now; 88 | return now; 89 | } 90 | 91 | private async Task ProcessFile(string file) 92 | { 93 | StreamReader stream = null; 94 | try 95 | { 96 | var tracking = new Host("FTP", IPAddress.Loopback); 97 | var header = new VideoHeader(tracking, file); 98 | if (!destination.IsDownloaded(header)) 99 | { 100 | log.Info("Copy <{0}>", file); 101 | stream = new StreamReader(file); 102 | await destination.Transfer(header, stream.BaseStream).ConfigureAwait(false); 103 | } 104 | else 105 | { 106 | log.Info("File is already copied <{0}", file); 107 | } 108 | } 109 | catch (Exception ex) 110 | { 111 | log.Error(ex); 112 | } 113 | finally 114 | { 115 | stream?.Dispose(); 116 | } 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/YiScanner/Downloader/FtpDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Text.RegularExpressions; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using FluentFTP; 8 | using NLog; 9 | using Wikiled.Common.Arguments; 10 | using Wikiled.YiScanner.Client; 11 | using Wikiled.YiScanner.Client.Predicates; 12 | using Wikiled.YiScanner.Destinations; 13 | using Wikiled.YiScanner.Monitoring.Source; 14 | 15 | namespace Wikiled.YiScanner.Downloader 16 | { 17 | public class FtpDownloader : IDownloader 18 | { 19 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 20 | 21 | private readonly HostTracking tracking; 22 | 23 | private readonly IDestination destination; 24 | 25 | private readonly Regex maskRegex; 26 | 27 | private readonly IPredicate predicate; 28 | 29 | public FtpDownloader( 30 | HostTracking tracking, 31 | IDestination destination, 32 | IPredicate predicate) 33 | { 34 | Guard.NotNull(() => tracking, tracking); 35 | Guard.NotNull(() => destination, destination); 36 | Guard.NotNull(() => predicate, predicate); 37 | maskRegex = FileMask.GenerateFitMask(tracking.Config.FileMask); 38 | log.Debug("Generated mask: {0}", maskRegex); 39 | this.tracking = tracking; 40 | this.destination = destination; 41 | this.predicate = predicate; 42 | } 43 | 44 | public async Task Download(CancellationToken cancellation) 45 | { 46 | // Get the object used to communicate with the server. 47 | using (var client = new FtpClient(tracking.Host.Address.ToString())) 48 | { 49 | log.Info("Connecting: {0}", tracking.Host.Address); 50 | client.Credentials = new NetworkCredential( 51 | tracking.Config.Login, 52 | tracking.Config.Password); 53 | client.Connect(); 54 | log.Info("Connected: {0}!", tracking.Host.Address); 55 | await Retrieve(client, tracking.Config.Path, cancellation).ConfigureAwait(false); 56 | } 57 | 58 | var now = tracking.Scanned(); 59 | return now; 60 | } 61 | 62 | private async Task ProcessFile(FtpClient client, FtpListItem item) 63 | { 64 | Stream stream = null; 65 | try 66 | { 67 | var header = new VideoHeader(tracking.Host, item.FullName); 68 | if (!destination.IsDownloaded(header)) 69 | { 70 | log.Info("Downloading <{0}> from [{1}]", item.FullName, tracking.Host.Name); 71 | stream = await client.OpenReadAsync(item.FullName).ConfigureAwait(false); 72 | await destination.Transfer(header, stream).ConfigureAwait(false); 73 | var reply = await client.GetReplyAsync(CancellationToken.None).ConfigureAwait(false); 74 | if (reply.Success) 75 | { 76 | log.Info( 77 | "Download Success:{0} Message:{1}: Type:{2} Code:{3} From: [{4}]", 78 | reply.Success, 79 | reply.Message, 80 | reply.Type, 81 | reply.Code, 82 | tracking.Host.Name); 83 | } 84 | else 85 | { 86 | log.Error( 87 | "Download Error:{0} Type:{1}: Code:{2} From: [{3}]", 88 | reply.ErrorMessage, 89 | reply.Type, 90 | reply.Code, 91 | tracking.Host.Name); 92 | } 93 | } 94 | else 95 | { 96 | log.Info("File is already downloaded - <{0}> {1}", item.FullName, tracking.Host.Name); 97 | } 98 | } 99 | catch (Exception ex) 100 | { 101 | log.Error(ex); 102 | } 103 | finally 104 | { 105 | stream?.Dispose(); 106 | } 107 | } 108 | 109 | private async Task Retrieve(FtpClient client, string path, CancellationToken cancellation) 110 | { 111 | foreach (FtpListItem item in client.GetListing(path)) 112 | { 113 | cancellation.ThrowIfCancellationRequested(); 114 | if (item.Type == FtpFileSystemObjectType.File) 115 | { 116 | if (maskRegex.IsMatch(item.FullName) && 117 | predicate.CanDownload(tracking.LastScanned, item.FullName, item.Modified)) 118 | { 119 | if (item.Modified < DateTime.Now.AddMinutes(1)) 120 | { 121 | await ProcessFile(client, item).ConfigureAwait(false); 122 | predicate.Downloaded(item.FullName); 123 | } 124 | else 125 | { 126 | log.Debug("Ignoring file - too recent: <{0}>", item.FullName); 127 | } 128 | } 129 | } 130 | else if (item.Type == FtpFileSystemObjectType.Directory) 131 | { 132 | await Retrieve(client, item.FullName, cancellation).ConfigureAwait(false); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/YiScanner/Downloader/IDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Wikiled.YiScanner.Downloader 6 | { 7 | public interface IDownloader 8 | { 9 | Task Download(CancellationToken cancellation); 10 | } 11 | } -------------------------------------------------------------------------------- /src/YiScanner/Helpers/ObservableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Concurrency; 3 | using System.Reactive.Linq; 4 | 5 | namespace Wikiled.YiScanner.Helpers 6 | { 7 | public static class ObservableExtensions 8 | { 9 | public static IObservable RepeatAfterDelay(this IObservable source, TimeSpan delay, IScheduler scheduler) 10 | { 11 | var repeatSignal = Observable 12 | .Empty() 13 | .Delay(delay, scheduler); 14 | 15 | // when source finishes, wait for the specified 16 | // delay, then repeat. 17 | return source.Concat(repeatSignal).Repeat(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/AutoDiscoveryConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring.Config 2 | { 3 | public class AutoDiscoveryConfig 4 | { 5 | public bool? On { get; set; } 6 | 7 | public string NetworkMask { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/FtpConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring.Config 2 | { 3 | public class FtpConfig 4 | { 5 | public string Path { get; set; } 6 | 7 | public string Password { get; set; } 8 | 9 | public string Login { get; set; } 10 | 11 | public string FileMask { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/MonitoringConfig.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using Wikiled.YiScanner.Destinations; 3 | 4 | namespace Wikiled.YiScanner.Monitoring.Config 5 | { 6 | public class MonitoringConfig 7 | { 8 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 9 | 10 | public int Scan { get; set; } 11 | 12 | public int? TimeOut { get; set; } 13 | 14 | public PredefinedCameraConfig Known { get; set; } 15 | 16 | public OutputConfig Output { get; set; } 17 | 18 | public int? Archive { get; set; } 19 | 20 | public ActionConfig Action { get; set; } 21 | 22 | public AutoDiscoveryConfig AutoDiscovery { get; set; } 23 | 24 | public FtpConfig YiFtp { get; set; } 25 | 26 | public ServerConfig Server { get; set; } 27 | 28 | public bool Validate() 29 | { 30 | if (Output == null) 31 | { 32 | log.Error("Output is not defined"); 33 | return false; 34 | } 35 | 36 | if (YiFtp == null) 37 | { 38 | log.Error("Yi Ftp is not defined"); 39 | return false; 40 | } 41 | 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/OutputConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring.Config 2 | { 3 | public class OutputConfig 4 | { 5 | public bool Compress { get; set; } 6 | 7 | public bool Images { get; set; } 8 | 9 | public string Out { get; set; } 10 | 11 | public bool All { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/PredefinedCameraConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring.Config 2 | { 3 | public class PredefinedCameraConfig 4 | { 5 | public string Cameras { get; set; } 6 | 7 | public string Hosts { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Config/ServerConfig.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring.Config 2 | { 3 | public class ServerConfig 4 | { 5 | public string Path { get; set; } 6 | 7 | public int Port { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/LiveDestinationFactory.cs: -------------------------------------------------------------------------------- 1 | namespace Wikiled.YiScanner.Monitoring 2 | { 3 | class LiveSourceFactory 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/MonitoringInstance.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reactive.Concurrency; 5 | using System.Reactive.Linq; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using NLog; 9 | using Wikiled.Common.Arguments; 10 | using Wikiled.YiScanner.Client.Archive; 11 | using Wikiled.YiScanner.Monitoring.Config; 12 | using Wikiled.YiScanner.Monitoring.Source; 13 | using Wikiled.YiScanner.Network; 14 | using Wikiled.YiScanner.Server; 15 | 16 | namespace Wikiled.YiScanner.Monitoring 17 | { 18 | public class MonitoringInstance 19 | { 20 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 21 | 22 | private readonly MonitoringConfig configuration; 23 | 24 | private readonly IDeleteArchiving archiving; 25 | 26 | private readonly List connections = new List(); 27 | 28 | private readonly IScheduler scheduler; 29 | 30 | private readonly ISourceFactory downloaderFactory; 31 | 32 | private IHostManager hostManager; 33 | 34 | private ServerManager server; 35 | 36 | public MonitoringInstance(IScheduler scheduler, MonitoringConfig configuration, ISourceFactory downloaderFactory, IDeleteArchiving archiving) 37 | { 38 | Guard.NotNull(() => configuration, configuration); 39 | Guard.NotNull(() => scheduler, scheduler); 40 | Guard.NotNull(() => downloaderFactory, downloaderFactory); 41 | Guard.NotNull(() => archiving, archiving); 42 | this.configuration = configuration; 43 | this.archiving = archiving; 44 | this.scheduler = scheduler; 45 | this.downloaderFactory = downloaderFactory; 46 | } 47 | 48 | public bool Start() 49 | { 50 | hostManager = configuration.AutoDiscovery?.On == true ? 51 | new DynamicHostManager(configuration, new NetworkScanner(TaskPoolScheduler.Default), scheduler) : 52 | (IHostManager)new StaticHostManager(configuration.Known); 53 | if (configuration.Archive.HasValue) 54 | { 55 | var archivingObservable = Observable.Empty() 56 | .Delay(TimeSpan.FromDays(1), scheduler) 57 | .Concat(Observable.FromAsync(Archiving, scheduler)) 58 | .Repeat() 59 | .SubscribeOn(scheduler) 60 | .Subscribe(); 61 | connections.Add(archivingObservable); 62 | } 63 | 64 | if (configuration.Server != null) 65 | { 66 | log.Info("Setting FTP server"); 67 | server = new ServerManager(configuration.Server); 68 | server.Start(); 69 | } 70 | 71 | var observableMonitor = Observable.Empty() 72 | .Delay(TimeSpan.FromSeconds(configuration.Scan), scheduler) 73 | .Concat(Observable.FromAsync(Download, scheduler)) 74 | .Repeat() 75 | .SubscribeOn(scheduler) 76 | .Subscribe(); 77 | 78 | connections.Add(observableMonitor); 79 | return true; 80 | } 81 | 82 | public void Stop() 83 | { 84 | hostManager.Dispose(); 85 | foreach (var connection in connections) 86 | { 87 | connection.Dispose(); 88 | } 89 | 90 | server?.Dispose(); 91 | connections.Clear(); 92 | } 93 | 94 | private async Task Archiving() 95 | { 96 | log.Info("Archiving..."); 97 | await archiving.Archive(configuration.Output.Out, TimeSpan.FromDays(configuration.Archive.Value)).ConfigureAwait(false); 98 | log.Info("Archiving. Done!"); 99 | return true; 100 | } 101 | 102 | private async Task Download() 103 | { 104 | log.Info("Checking Ftp...."); 105 | try 106 | { 107 | var sources = downloaderFactory.GetSources(hostManager).ToArray(); 108 | List tasks = new List(); 109 | log.Info("Downloading from {0} cameras", sources.Length); 110 | CancellationTokenSource token = new CancellationTokenSource(); 111 | 112 | foreach (var item in sources) 113 | { 114 | tasks.Add(item.Download(token.Token)); 115 | } 116 | 117 | if (configuration.TimeOut.HasValue) 118 | { 119 | var time = TimeSpan.FromSeconds(configuration.TimeOut.Value); 120 | token.CancelAfter(time); 121 | var timeoutTask = Task.Delay(time, token.Token); 122 | await Task.WhenAny(Task.WhenAll(tasks), timeoutTask).ConfigureAwait(false); 123 | if (timeoutTask.IsCompleted) 124 | { 125 | log.Error("FTP processing Timout!"); 126 | return false; 127 | } 128 | } 129 | else 130 | { 131 | await Task.WhenAll(tasks).ConfigureAwait(false); 132 | } 133 | 134 | log.Info("Done!"); 135 | return true; 136 | } 137 | catch (Exception ex) 138 | { 139 | log.Error(ex); 140 | } 141 | 142 | return false; 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/ServiceStarter.cs: -------------------------------------------------------------------------------- 1 | using System.Reactive.Concurrency; 2 | using NLog; 3 | using Topshelf; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.YiScanner.Client.Archive; 6 | using Wikiled.YiScanner.Client.Predicates; 7 | using Wikiled.YiScanner.Monitoring.Config; 8 | using Wikiled.YiScanner.Monitoring.Source; 9 | 10 | namespace Wikiled.YiScanner.Monitoring 11 | { 12 | public class ServiceStarter 13 | { 14 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 15 | 16 | public void StartService(MonitoringConfig config) 17 | { 18 | Guard.NotNull(() => config, config); 19 | var predicate = config.Output.All ? new NullPredicate() : (IPredicate)new NewFilesPredicate(); 20 | SourceFactory factory = new SourceFactory(config, predicate); 21 | HostFactory.Run( 22 | x => 23 | { 24 | x.Service( 25 | s => 26 | { 27 | s.ConstructUsing(name => new MonitoringInstance(TaskPoolScheduler.Default, config, factory, new DeleteArchiving())); 28 | s.WhenStarted(tc => tc.Start()); 29 | s.WhenStopped(tc => tc.Stop()); 30 | }); 31 | 32 | x.RunAsLocalSystem(); 33 | x.SetDescription("Camera Monitoring Service"); 34 | x.SetDisplayName("YiScanner Service"); 35 | x.SetServiceName("YiScanner"); 36 | }); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/DynamicHostManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Threading.Tasks; 9 | using NLog; 10 | using Wikiled.Common.Arguments; 11 | using Wikiled.YiScanner.Monitoring.Config; 12 | using Wikiled.YiScanner.Network; 13 | 14 | namespace Wikiled.YiScanner.Monitoring.Source 15 | { 16 | public class DynamicHostManager : IHostManager 17 | { 18 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 19 | 20 | private readonly ConcurrentDictionary result = new ConcurrentDictionary(); 21 | 22 | private readonly IDisposable subscription; 23 | 24 | private readonly INetworkScanner scanner; 25 | 26 | private readonly MonitoringConfig config; 27 | 28 | public DynamicHostManager(MonitoringConfig config, INetworkScanner scanner, IScheduler scheduler) 29 | { 30 | Guard.NotNull(() => config, config); 31 | Guard.NotNull(() => scanner, scanner); 32 | Guard.NotNull(() => scheduler, scheduler); 33 | 34 | this.scanner = scanner; 35 | this.config = config; 36 | subscription = Observable.FromAsync(ScanFtp, scheduler).Delay(TimeSpan.FromSeconds(10), scheduler).Repeat().SubscribeOn(scheduler).Subscribe(); 37 | } 38 | 39 | public void Dispose() 40 | { 41 | subscription.Dispose(); 42 | } 43 | 44 | public IEnumerable GetHosts() 45 | { 46 | return result.Values; 47 | } 48 | 49 | private async Task ScanFtp() 50 | { 51 | log.Debug("ScanFtp"); 52 | ConcurrentDictionary thisCycle = new ConcurrentDictionary(); 53 | await scanner.FindAddresses(config.AutoDiscovery.NetworkMask, 21) 54 | .ForEachAsync( 55 | item => 56 | { 57 | thisCycle[item.Address] = item; 58 | if (!result.ContainsKey(item.Address)) 59 | { 60 | log.Info("Adding new host: {0}", item.Address); 61 | result[item.Address] = item; 62 | } 63 | }) 64 | .ConfigureAwait(false); 65 | foreach (var host in result.Keys.ToArray()) 66 | { 67 | if (!thisCycle.ContainsKey(host)) 68 | { 69 | log.Info("Removing: {0}", host); 70 | result.TryRemove(host, out _); 71 | } 72 | } 73 | 74 | return true; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/Host.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace Wikiled.YiScanner.Monitoring.Source 4 | { 5 | public class Host 6 | { 7 | public Host(string name, IPAddress address, int port = 21) 8 | { 9 | Name = name; 10 | Address = address; 11 | Port = port; 12 | } 13 | 14 | public string Name { get; } 15 | 16 | public IPAddress Address { get; } 17 | 18 | public int Port { get; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/HostTracking.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Wikiled.Common.Arguments; 3 | using Wikiled.YiScanner.Client; 4 | using Wikiled.YiScanner.Monitoring.Config; 5 | 6 | namespace Wikiled.YiScanner.Monitoring.Source 7 | { 8 | public class HostTracking 9 | { 10 | public HostTracking(FtpConfig config, Host host) 11 | { 12 | Guard.NotNull(() => config, config); 13 | Guard.NotNull(() => host, host); 14 | Config = config; 15 | Host = host; 16 | LastScanned = null; 17 | } 18 | 19 | public FtpConfig Config { get; } 20 | 21 | public Host Host { get; } 22 | 23 | public DateTime? LastScanned { get; private set; } 24 | 25 | public DateTime Scanned() 26 | { 27 | var now = DateTime.Now; 28 | LastScanned = now; 29 | return now; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/IHostManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Wikiled.YiScanner.Monitoring.Source 5 | { 6 | public interface IHostManager : IDisposable 7 | { 8 | IEnumerable GetHosts(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/ISourceFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Wikiled.YiScanner.Client; 3 | using Wikiled.YiScanner.Downloader; 4 | 5 | namespace Wikiled.YiScanner.Monitoring.Source 6 | { 7 | public interface ISourceFactory 8 | { 9 | IEnumerable GetSources(IHostManager hosts); 10 | } 11 | } -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/SourceFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using NLog; 6 | using Wikiled.Common.Arguments; 7 | using Wikiled.YiScanner.Client; 8 | using Wikiled.YiScanner.Client.Predicates; 9 | using Wikiled.YiScanner.Destinations; 10 | using Wikiled.YiScanner.Downloader; 11 | using Wikiled.YiScanner.Monitoring.Config; 12 | 13 | namespace Wikiled.YiScanner.Monitoring.Source 14 | { 15 | public class SourceFactory : ISourceFactory 16 | { 17 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 18 | 19 | private readonly IPredicate filePredicate; 20 | 21 | private readonly MonitoringConfig config; 22 | 23 | private readonly List fixedDownloaders = new List(); 24 | 25 | private readonly ConcurrentDictionary trackingInformation = new ConcurrentDictionary(); 26 | 27 | private readonly IDestination destination; 28 | 29 | public SourceFactory(MonitoringConfig config, IPredicate filePredicate) 30 | { 31 | Guard.NotNull(() => config, config); 32 | Guard.NotNull(() => filePredicate, filePredicate); 33 | this.config = config; 34 | this.filePredicate = filePredicate; 35 | destination = ConstructDestination(); 36 | if (config.Server != null) 37 | { 38 | fixedDownloaders.Add(new FileDownloader(config.Server, destination, filePredicate)); 39 | } 40 | } 41 | 42 | public IEnumerable GetSources(IHostManager manager) 43 | { 44 | log.Info("Downloading..."); 45 | return fixedDownloaders.Union(manager.GetHosts().Select(item => ConstructDownloader(item, destination))); 46 | } 47 | 48 | private IDestination ConstructDestination() 49 | { 50 | IDestination destination = config.Output.Images ? (IDestination)new PictureFileDestination(config.Output.Out) : new FileDestination(config.Output.Out); 51 | if (config.Output.Compress) 52 | { 53 | destination = ChainedPriorActionDestination.CreateCompressed(destination); 54 | } 55 | 56 | if (config.Action != null) 57 | { 58 | destination = ChainedPostActionDestination.CreateAction(destination, config.Action); 59 | } 60 | 61 | return destination; 62 | } 63 | 64 | private IDownloader ConstructDownloader(Host host, IDestination destination) 65 | { 66 | if (!trackingInformation.TryGetValue(host.Address, out var downloader)) 67 | { 68 | var tracking = new HostTracking(config.YiFtp, host); 69 | downloader = new FtpDownloader(tracking, destination, filePredicate); 70 | trackingInformation[host.Address] = downloader; 71 | } 72 | 73 | return downloader; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/YiScanner/Monitoring/Source/StaticHostManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Net; 3 | using NLog; 4 | using Wikiled.Common.Arguments; 5 | using Wikiled.YiScanner.Monitoring.Config; 6 | 7 | namespace Wikiled.YiScanner.Monitoring.Source 8 | { 9 | public class StaticHostManager : IHostManager 10 | { 11 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 12 | 13 | private readonly PredefinedCameraConfig config; 14 | 15 | public StaticHostManager(PredefinedCameraConfig config) 16 | { 17 | Guard.NotNull(() => config, config); 18 | this.config = config; 19 | } 20 | 21 | public IEnumerable GetHosts() 22 | { 23 | return GetHostsInternal(); 24 | } 25 | 26 | public void Dispose() 27 | { 28 | } 29 | 30 | private IEnumerable GetHostsInternal() 31 | { 32 | if (string.IsNullOrEmpty(config.Cameras)) 33 | { 34 | log.Error("Invalid camera(s) names"); 35 | yield break; 36 | } 37 | 38 | if (string.IsNullOrEmpty(config.Hosts)) 39 | { 40 | log.Error("Invalid camera(s) hosts"); 41 | yield break; 42 | } 43 | 44 | var listOfCameras = config.Cameras.Split(','); 45 | var listOfHosts = config.Hosts.Split(','); 46 | if (listOfHosts.Length != listOfCameras.Length) 47 | { 48 | log.Error("List of camera names and hosts does not match"); 49 | yield break; 50 | } 51 | 52 | for (int i = 0; i < listOfCameras.Length; i++) 53 | { 54 | yield return new Host(listOfCameras[i], IPAddress.Parse(listOfHosts[i])); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/YiScanner/Network/INetworkScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Threading.Tasks; 5 | using Wikiled.YiScanner.Monitoring.Source; 6 | 7 | namespace Wikiled.YiScanner.Network 8 | { 9 | public interface INetworkScanner 10 | { 11 | IObservable FindAddresses(string network, int port); 12 | 13 | Task ScanPort(IPAddress address, int port); 14 | 15 | IEnumerable GetLocalIPAddress(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/YiScanner/Network/NetworkScanner.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Threading.Tasks; 9 | using Wikiled.Common.Arguments; 10 | using Wikiled.YiScanner.Monitoring.Source; 11 | 12 | namespace Wikiled.YiScanner.Network 13 | { 14 | public class NetworkScanner : INetworkScanner 15 | { 16 | private readonly ILookup localAddresses; 17 | 18 | private readonly IScheduler scheduler; 19 | 20 | public NetworkScanner(IScheduler scheduler) 21 | { 22 | Guard.NotNull(() => scheduler, scheduler); 23 | this.scheduler = scheduler; 24 | localAddresses = GetLocalIPAddress().ToLookup(item => item); 25 | } 26 | 27 | public IObservable FindAddresses(string network, int port) 28 | { 29 | IPNetwork ipNetwork = IPNetwork.Parse(network); 30 | return ipNetwork.ListIPAddress() 31 | .Where(item => !localAddresses.Contains(item)) 32 | .ToObservable() 33 | .SelectMany( 34 | item => Observable.Start(async () => (await ScanPort(item, port).ConfigureAwait(false), item), scheduler) 35 | .Merge() 36 | .Where(pair => pair.Item1) 37 | .Select(pair => new Host(GetHostName(pair.Item2), pair.Item2))); 38 | } 39 | 40 | public async Task ScanPort(IPAddress address, int port) 41 | { 42 | using (TcpClient client = new TcpClient()) 43 | { 44 | try 45 | { 46 | await client.ConnectAsync(address, port).ConfigureAwait(false); 47 | return client.Connected; 48 | } 49 | catch(Exception) 50 | { 51 | return false; 52 | } 53 | } 54 | } 55 | 56 | public IEnumerable GetLocalIPAddress() 57 | { 58 | var host = Dns.GetHostEntry(Dns.GetHostName()); 59 | foreach (IPAddress ip in host.AddressList) 60 | { 61 | if (ip.AddressFamily == AddressFamily.InterNetwork) 62 | { 63 | yield return ip; 64 | } 65 | } 66 | } 67 | 68 | private string GetHostName(IPAddress ipAddress) 69 | { 70 | try 71 | { 72 | IPHostEntry entry = Dns.GetHostEntry(ipAddress); 73 | return entry.HostName.Split('.')[0]; 74 | } 75 | catch(SocketException) 76 | { 77 | } 78 | 79 | return ipAddress.ToString(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/YiScanner/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reactive.Concurrency; 6 | using System.Reflection; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Linq; 9 | using NLog; 10 | using Wikiled.Console.Arguments; 11 | using Wikiled.YiScanner.Commands; 12 | using Wikiled.YiScanner.Monitoring; 13 | using Wikiled.YiScanner.Monitoring.Config; 14 | using Wikiled.YiScanner.Network; 15 | 16 | namespace Wikiled.YiScanner 17 | { 18 | public class Program 19 | { 20 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 21 | 22 | public static void Main(string[] args) 23 | { 24 | try 25 | { 26 | var directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 27 | if (!File.Exists(Path.Combine(directory, "service.json"))) 28 | { 29 | log.Error("Configuration file service.json not found"); 30 | return; 31 | } 32 | 33 | MonitoringConfig config = JsonConvert.DeserializeObject(File.ReadAllText(Path.Combine(directory, "service.json"))); 34 | if (!config.Validate()) 35 | { 36 | log.Error("Invalid configuration"); 37 | return; 38 | } 39 | 40 | NetworkScanner scanner = new NetworkScanner(TaskPoolScheduler.Default); 41 | log.Info("Starting {0} version utility...", Assembly.GetExecutingAssembly().GetName().Version); 42 | foreach (var address in scanner.GetLocalIPAddress()) 43 | { 44 | log.Info("Starting on local IP: [{0}]", address); 45 | } 46 | 47 | List commandsList = new List(); 48 | commandsList.Add(new MonitorCommand(config)); 49 | commandsList.Add(new DownloadCommand(config)); 50 | var commands = commandsList.ToDictionary(item => item.Name, item => item, StringComparer.OrdinalIgnoreCase); 51 | 52 | if (args.Length == 0 || 53 | !commands.TryGetValue(args[0], out var command)) 54 | { 55 | log.Info("Starting as service"); 56 | ServiceStarter serviceStarter = new ServiceStarter(); 57 | serviceStarter.StartService(config); 58 | return; 59 | } 60 | 61 | command.ParseArguments(args.Skip(1)); 62 | command.Execute(); 63 | } 64 | catch (Exception ex) 65 | { 66 | log.Error(ex); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/YiScanner/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Wikiled.YiScanner": { 4 | "commandName": "Project", 5 | "commandLineArgs": "Download -Cameras=1080i -Hosts=192.168.200.12 -Out=c:\\out" 6 | } 7 | } 8 | } -------------------------------------------------------------------------------- /src/YiScanner/Server/FtpLogForNLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FubarDev.FtpServer; 3 | using NLog; 4 | 5 | namespace Wikiled.YiScanner.Server 6 | { 7 | public class FtpLogForNLog : IFtpLog 8 | { 9 | private readonly ILogger logger; 10 | 11 | private readonly string remoteAddress; 12 | 13 | private readonly string remoteIp; 14 | 15 | private readonly int? remotePort; 16 | 17 | public FtpLogForNLog(FtpConnection connection) 18 | { 19 | logger = LogManager.GetLogger("FubarDev.FtpServer.FtpConnection"); 20 | remoteAddress = connection.RemoteAddress.ToString(true); 21 | remoteIp = connection.RemoteAddress.IpAddress; 22 | remotePort = connection.RemoteAddress.IpPort; 23 | } 24 | 25 | public FtpLogForNLog(Type type) 26 | { 27 | logger = LogManager.GetLogger(type.FullName); 28 | } 29 | 30 | public FtpLogForNLog(string name) 31 | { 32 | logger = LogManager.GetLogger(name); 33 | } 34 | 35 | public void Trace(string format, params object[] args) 36 | { 37 | Log(LogLevel.Trace, null, format, args); 38 | } 39 | 40 | public void Trace(Exception ex, string format, params object[] args) 41 | { 42 | Log(LogLevel.Trace, ex, format, args); 43 | } 44 | 45 | public void Debug(string format, params object[] args) 46 | { 47 | Log(LogLevel.Debug, null, format, args); 48 | } 49 | 50 | public void Debug(Exception ex, string format, params object[] args) 51 | { 52 | Log(LogLevel.Debug, ex, format, args); 53 | } 54 | 55 | public void Info(string format, params object[] args) 56 | { 57 | Log(LogLevel.Info, null, format, args); 58 | } 59 | 60 | public void Info(Exception ex, string format, params object[] args) 61 | { 62 | Log(LogLevel.Info, ex, format, args); 63 | } 64 | 65 | public void Warn(string format, params object[] args) 66 | { 67 | Log(LogLevel.Warn, null, format, args); 68 | } 69 | 70 | public void Warn(Exception ex, string format, params object[] args) 71 | { 72 | Log(LogLevel.Warn, ex, format, args); 73 | } 74 | 75 | public void Error(string format, params object[] args) 76 | { 77 | Log(LogLevel.Error, null, format, args); 78 | } 79 | 80 | public void Error(Exception ex, string format, params object[] args) 81 | { 82 | Log(LogLevel.Error, ex, format, args); 83 | } 84 | 85 | public void Fatal(string format, params object[] args) 86 | { 87 | Log(LogLevel.Fatal, null, format, args); 88 | } 89 | 90 | public void Fatal(Exception ex, string format, params object[] args) 91 | { 92 | Log(LogLevel.Fatal, ex, format, args); 93 | } 94 | 95 | private void Log(LogLevel logLevel, Exception ex, string format, params object[] args) 96 | { 97 | var message = args.Length == 0 ? format : string.Format(format, args); 98 | logger.Log(new LogEventInfo(logLevel, logger.Name, message) 99 | { 100 | Properties = 101 | { 102 | ["RemoteAddress"] = remoteAddress, 103 | ["RemoteIp"] = remoteIp, 104 | ["RemotePort"] = remotePort, 105 | }, 106 | Exception = ex, 107 | }); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/YiScanner/Server/FtpLogManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using FubarDev.FtpServer; 3 | 4 | namespace Wikiled.YiScanner.Server 5 | { 6 | public class FtpLogManager : IFtpLogManager 7 | { 8 | public IFtpLog CreateLog(FtpConnection connection) 9 | { 10 | return new FtpLogForNLog(connection); 11 | } 12 | 13 | public IFtpLog CreateLog(string name) 14 | { 15 | return new FtpLogForNLog(name); 16 | } 17 | 18 | public IFtpLog CreateLog(Type type) 19 | { 20 | return new FtpLogForNLog(type); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/YiScanner/Server/IServerManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Wikiled.YiScanner.Server 4 | { 5 | public interface IServerManager : IDisposable 6 | { 7 | void Start(); 8 | } 9 | } -------------------------------------------------------------------------------- /src/YiScanner/Server/ServerManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Reflection; 4 | using FubarDev.FtpServer; 5 | using FubarDev.FtpServer.AccountManagement; 6 | using FubarDev.FtpServer.AccountManagement.Anonymous; 7 | using FubarDev.FtpServer.FileSystem.DotNet; 8 | using NLog; 9 | using Wikiled.Common.Arguments; 10 | using Wikiled.Common.Extensions; 11 | using Wikiled.YiScanner.Monitoring.Config; 12 | 13 | namespace Wikiled.YiScanner.Server 14 | { 15 | public class ServerManager : IServerManager 16 | { 17 | private static readonly Logger log = LogManager.GetCurrentClassLogger(); 18 | 19 | private readonly ServerConfig config; 20 | 21 | private FtpServer ftpServer; 22 | 23 | public ServerManager(ServerConfig config) 24 | { 25 | Guard.NotNull(() => config, config); 26 | this.config = config; 27 | } 28 | 29 | public void Dispose() 30 | { 31 | ftpServer?.Dispose(); 32 | } 33 | 34 | public void Start() 35 | { 36 | var outPath = Path.Combine(Environment.CurrentDirectory, config.Path); 37 | outPath.EnsureDirectoryExistence(); 38 | log.Debug("Start FTP server: [{0}]", outPath); 39 | var membershipProvider = new AnonymousMembershipProvider(new NoValidation()); 40 | var provider = new DotNetFileSystemProvider(outPath, false); 41 | 42 | // Initialize the FTP server 43 | ftpServer = new FtpServer(provider, membershipProvider, "127.0.0.1", config.Port, new AssemblyFtpCommandHandlerFactory(typeof(FtpServer).GetTypeInfo().Assembly)); 44 | ftpServer.LogManager = new FtpLogManager(); 45 | 46 | // Start the FTP server 47 | ftpServer.Start(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/YiScanner/Wikiled.YiScanner.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Wikiled.YiScanner 5 | Wikiled.YiScanner.Program 6 | Exe 7 | NET462 8 | win7-x86 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/YiScanner/nlog.config: -------------------------------------------------------------------------------- 1 |  2 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/YiScanner/service.json: -------------------------------------------------------------------------------- 1 | { 2 | "Scan": 30, 3 | "Timeout": 1200, 4 | "Output": { 5 | "Compress": false, 6 | "Images": false, 7 | "Out": "D:/Cloud/GoogleUni/Camera/Monitor" 8 | }, 9 | "Archive": 2, 10 | "Action": null, 11 | "AutoDiscovery": { 12 | "On": true, 13 | "NetworkMask": "192.168.0.0/255.255.255.0" 14 | }, 15 | 16 | "YiFtp": { 17 | "Path": "/tmp/sd/record/", 18 | "Password": "", 19 | "Login": "root", 20 | "FileMask": "*.mp4" 21 | }, 22 | 23 | "Server": { 24 | "Path": "CCTV" 25 | } 26 | } --------------------------------------------------------------------------------