Newer
Older
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.IO;
using System.Timers;
using System.DirectoryServices.AccountManagement;
using System.Management;
Niko Ehrenfeuchter
committed
using AutoTx.XmlWrapper;
using RoboSharp;
namespace AutoTx
{
public partial class AutoTx : ServiceBase
{
#region global variables
Niko Ehrenfeuchter
committed
// naming convention: variables ending with "Path" are strings, variables
// ending with "Dir" are DirectoryInfo objects
private string _configPath;
private string _statusPath;
private string _incomingPath;
private string _managedPath;
private string[] _remoteUserDirs;
private string[] _localUserDirs;
private List<string> _transferredFiles = new List<string>();
private int _txProgress;
private const int MegaBytes = 1024 * 1024;
private const int GigaBytes = 1024 * 1024 * 1024;
private DateTime _lastUserDirCheck = DateTime.Now;
// the transfer state:
private enum TxState
{
Stopped = 0,
// Stopped: the last transfer was finished successfully or none was started yet.
// A new transfer may only be started if the service is in this state.
Active = 1,
// Active: a transfer is currently running (i.e. no new transfer may be started).
Paused = 2,
// Paused is assigned in PauseTransfer() which gets called from within RunMainTasks()
// when system parameters are not in their valid range. It gets evaluated if the
// parameters return to valid or if no user is logged on any more.
DoNothing = 3
// DoNothing is assigned when the service gets shut down (in the OnStop() method)
// to prevent accidentially launching new transfers etc.
}
private TxState _transferState;
Niko Ehrenfeuchter
committed
private ServiceConfig _config;
private ServiceStatus _status;
private static Timer _mainTimer;
#endregion
#region initialize, load and check configuration + status
public AutoTx() {
InitializeComponent();
CreateEventLog();
LoadSettings();
CreateIncomingDirectories();
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
}
/// <summary>
/// Create the event log if it doesn't exist yet.
/// </summary>
private void CreateEventLog() {
try {
if (!EventLog.SourceExists(ServiceName)) {
EventLog.CreateEventSource(
ServiceName + "Log", ServiceName);
}
eventLog.Source = ServiceName + "Log";
eventLog.Log = ServiceName;
}
catch (Exception ex) {
writeLog("Error in createEventLog(): " + ex.Message, true);
}
}
/// <summary>
/// Loads the initial settings.
/// </summary>
private void LoadSettings() {
try {
_transferState = TxState.Stopped;
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
_logPath = Path.Combine(baseDir, "service.log");
_configPath = Path.Combine(baseDir, "configuration.xml");
_statusPath = Path.Combine(baseDir, "status.xml");
LoadConfigStatusXml();
_roboCommand = new RoboCommand();
}
catch (Exception ex) {
writeLog("Error in LoadSettings(): " + ex.Message, true);
throw new Exception("Error in LoadSettings.");
}
// NOTE: this is explicitly called *outside* the try-catch block so an Exception
// thrown by the checker will not be silenced but cause the startup to fail:
CheckConfiguration();
}
/// <summary>
/// Loads the configuration and status xml files.
/// </summary>
private void LoadConfigStatusXml() {
try {
Niko Ehrenfeuchter
committed
_config = ServiceConfig.Deserialize(_configPath);
writeLogDebug("Loaded config from " + _configPath);
}
catch (Exception ex) {
writeLog("Error loading configuration XML: " + ex.Message, true);
// this should terminate the service process:
throw new Exception("Error loading config.");
}
try {
Niko Ehrenfeuchter
committed
_status = ServiceStatus.Deserialize(_statusPath);
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
writeLogDebug("Loaded status from " + _statusPath);
}
catch (Exception ex) {
writeLog("Error loading status XML: " + ex.Message, true);
// this should terminate the service process:
throw new Exception("Error loading status.");
}
}
/// <summary>
/// Check if loaded configuration is valid, print a summary to the log.
/// </summary>
public void CheckConfiguration() {
var configInvalid = false;
try {
if (string.IsNullOrEmpty(_config.SourceDrive) ||
string.IsNullOrEmpty(_config.IncomingDirectory) ||
string.IsNullOrEmpty(_config.ManagedDirectory)) {
writeLog("ERROR: mandatory parameter missing!");
configInvalid = true;
}
if (_config.SourceDrive.Substring(1) != @":\") {
writeLog("ERROR: SourceDrive must be a drive letter followed by a colon" +
@" and a backslash, e.g. 'D:\'!");
configInvalid = true;
}
// spooling directories: IncomingDirectory + ManagedDirectory
if (_config.IncomingDirectory.StartsWith(@"\")) {
writeLog("ERROR: IncomingDirectory must not start with a backslash!");
configInvalid = true;
}
if (_config.ManagedDirectory.StartsWith(@"\")) {
writeLog("ERROR: ManagedDirectory must not start with a backslash!");
configInvalid = true;
}
_incomingPath = Path.Combine(_config.SourceDrive, _config.IncomingDirectory);
_managedPath = Path.Combine(_config.SourceDrive, _config.ManagedDirectory);
if (CheckSpoolingDirectories() == false) {
writeLog("ERROR checking spooling directories (incoming / managed)!");
configInvalid = true;
}
// DestinationDirectory
if (!_config.DestinationDirectory.StartsWith(@"\\")) {
writeLog("WARNING: DestinationDirectory is no UNC path!");
}
if (!Directory.Exists(_config.DestinationDirectory)) {
writeLog("ERROR: can't find destination: " + _config.DestinationDirectory);
configInvalid = true;
}
// TmpTransferDir
var tmpTransferPath = Path.Combine(_config.DestinationDirectory,
_config.TmpTransferDir);
if (!Directory.Exists(tmpTransferPath)) {
writeLog("ERROR: temporary transfer dir doesn't exist: " + tmpTransferPath);
configInvalid = true;
}
// CurrentTransferSrc
if (_status.CurrentTransferSrc.Length > 0
&& !Directory.Exists(_status.CurrentTransferSrc)) {
writeLog("WARNING: status file contains non-existing source path of an " +
"unfinished transfer: " + _status.CurrentTransferSrc);
_status.CurrentTransferSrc = "";
}
// CurrentTargetTmp
if (_status.CurrentTargetTmp.Length > 0
&& !Directory.Exists(ExpandCurrentTargetTmp())) {
writeLog("WARNING: status file contains non-existing temporary path of an " +
"unfinished transfer: " + _status.CurrentTargetTmp);
_status.CurrentTargetTmp = "";
}
if (_config.ServiceTimer < 1000) {
writeLog("ERROR: ServiceTimer must not be smaller than 1000 ms!");
configInvalid = true;
}
}
catch (Exception ex) {
writeLog("Error in CheckConfiguration(): " + ex.Message);
configInvalid = true;
}
// terminate the service process if necessary:
if (configInvalid) throw new Exception("Invalid config, check log file!");
// check the clean-shutdown status and send a notification if it was not true,
// then set it to false while the service is running until it is properly
// shut down via the OnStop() method:
if (_status.CleanShutdown == false) {
writeLog("WARNING: " + ServiceName + " was not shut down properly last time!\n\n" +
"This could indicate the computer has crashed or was forcefully shut off.", true);
}
_status.CleanShutdown = false;
StartupSummary();
}
/// <summary>
/// Write a summary of loaded config + status to the log.
/// </summary>
private void StartupSummary() {
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
writeLogDebug("------ RoboSharp ------");
var roboDll = System.Reflection.Assembly.GetAssembly(typeof(RoboCommand)).Location;
if (roboDll != null) {
var versionInfo = FileVersionInfo.GetVersionInfo(roboDll);
writeLogDebug(" > DLL file: " + roboDll);
writeLogDebug(" > DLL description: " + versionInfo.Comments);
writeLogDebug(" > DLL version: " + versionInfo.FileVersion);
}
writeLogDebug("");
writeLogDebug("------ Loaded status flags ------");
writeLogDebug("CurrentTransferSrc: " + _status.CurrentTransferSrc);
writeLogDebug("CurrentTargetTmp: " + _status.CurrentTargetTmp);
writeLogDebug("FilecopyFinished: " + _status.FilecopyFinished);
writeLogDebug("LastStorageNotification: " +
_status.LastStorageNotification.ToString("yyyy-MM-dd HH:mm:ss"));
writeLogDebug("LastAdminNotification: " +
_status.LastAdminNotification.ToString("yyyy-MM-dd HH:mm:ss"));
writeLogDebug("");
writeLogDebug("------ Loaded configuration settings ------");
writeLogDebug("HostAlias: " + _config.HostAlias);
writeLogDebug("SourceDrive: " + _config.SourceDrive);
writeLogDebug("IncomingDirectory: " + _config.IncomingDirectory);
writeLogDebug("MarkerFile: " + _config.MarkerFile);
writeLogDebug("ManagedDirectory: " + _config.ManagedDirectory);
writeLogDebug("DestinationDirectory: " + _config.DestinationDirectory);
writeLogDebug("TmpTransferDir: " + _config.TmpTransferDir);
writeLogDebug("ServiceTimer: " + _config.ServiceTimer);
writeLogDebug("InterPacketGap: " + _config.InterPacketGap);
writeLogDebug("MaxCpuUsage: " + _config.MaxCpuUsage);
writeLogDebug("MinAvailableMemory: " + _config.MinAvailableMemory);
foreach (var processName in _config.BlacklistedProcesses) {
writeLogDebug("BlacklistedProcess: " + processName);
}
foreach (var driveToCheck in _config.SpaceMonitoring) {
writeLogDebug("Drive to check free space: " + driveToCheck.DriveName +
" (threshold: " + driveToCheck.SpaceThreshold + ")");
}
writeLogDebug("StorageNotificationDelta: " + _config.StorageNotificationDelta);
writeLogDebug("AdminNotificationDelta: " + _config.AdminNotificationDelta);
if (string.IsNullOrEmpty(_config.SmtpHost)) {
writeLogDebug("SmtpHost: ====== Not configured, disabling notification emails! ======");
} else {
writeLogDebug("SmtpHost: " + _config.SmtpHost);
}
writeLogDebug("");
// finally some current information:
writeLogDebug("------ Current system parameters ------");
writeLogDebug("Hostname: " + Environment.MachineName);
writeLogDebug("Free system memory: " + GetFreeMemory() + " MB");
foreach (var driveToCheck in _config.SpaceMonitoring) {
writeLogDebug("Free space on drive '" + driveToCheck.DriveName + "': " +
GetFreeDriveSpace(driveToCheck.DriveName));
}
writeLogDebug("");
}
#endregion
#region overrides for ServiceBase methods (start, stop, ...)
/// <summary>
/// Is executed when the service starts
/// </summary>
protected override void OnStart(string[] args) {
try {
_mainTimer = new Timer(_config.ServiceTimer);
_mainTimer.Elapsed += OnTimedEvent;
_mainTimer.Enabled = true;
}
catch (Exception ex) {
writeLog("Error in OnStart(): " + ex.Message, true);
}
// read the build timestamp from the resources:
var buildTimestamp = Properties.Resources.BuildDate.Trim();
writeLog("-----------------------");
writeLog(ServiceName + " service started <build " + buildTimestamp + ">");
writeLog("-----------------------");
}
/// <summary>
/// Executes when a Stop command is sent to the service by the Service Control Manager.
/// NOTE: the method is NOT triggered when the operating system shuts down, instead
/// the OnShutdown() method is used!
/// </summary>
protected override void OnStop() {
writeLog(ServiceName + " service stop requested...");
if (_transferState != TxState.Stopped) {
_transferState = TxState.DoNothing;
// Stop() is calling Process.Kill() (immediately forcing a termination of the
// process, returning asynchronously), followed by Process.Dispose()
// (releasing all resources used by the component). Would be nice if RoboSharp
// implemented a method to check if the process has actually terminated, but
// this is probably something we have to do ourselves.
try {
_roboCommand.Stop();
}
catch (Exception ex) {
writeLog("Error terminating the RoboCopy process: " + ex.Message, true);
}
_status.FilecopyFinished = false;
writeLog("Not all files were transferred - will resume upon next start");
writeLogDebug("CurrentTransferSrc: " + _status.CurrentTransferSrc);
// should we delete an incompletely transferred file on the target?
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
}
// set the shutdown status to clean:
_status.CleanShutdown = true;
writeLog("-----------------------");
writeLog(ServiceName + " service stopped");
writeLog("-----------------------");
}
/// <summary>
/// Executes when the operating system is shutting down. Unlike some documentation says
/// it doesn't call the OnStop() method, so we have to do this explicitly.
/// </summary>
protected override void OnShutdown() {
writeLog("System is shutting down, requesting the service to stop.");
OnStop();
}
/// <summary>
/// Is executed when the service continues
/// </summary>
protected override void OnContinue() {
writeLog("-------------------------");
writeLog(ServiceName + " service resuming");
writeLog("-------------------------");
}
/// <summary>
/// Timer Event
/// </summary>
private void OnTimedEvent(object source, ElapsedEventArgs e) {
if (_transferState == TxState.DoNothing) return;
// first disable the timer event to prevent another one from being triggered
// while this method has not finished yet:
_mainTimer.Enabled = false;
try {
RunMainTasks();
GC.Collect();
}
catch (Exception ex) {
writeLog("Error in OnTimedEvent(): " + ex.Message, true);
writeLogDebug("Extended Error Info (StackTrace): " + ex.StackTrace);
}
finally {
// make sure to enable the timer again:
_mainTimer.Enabled = true;
}
}
#endregion
#region general methods
/// <summary>
/// Check system parameters for valid ranges and update the global service state accordingly.
/// </summary>
private void UpdateServiceState() {
var limitReason = "";
// check all system parameters for valid ranges and remember the reason in a string
// if one of them is failing (to report in the log why we're suspended)
if (GetCpuUsage() >= _config.MaxCpuUsage)
limitReason = "CPU usage";
else if (GetFreeMemory() < _config.MinAvailableMemory)
limitReason = "RAM usage";
else {
var blacklistedProcess = CheckForBlacklistedProcesses();
if (blacklistedProcess != "") {
limitReason = "blacklisted process '" + blacklistedProcess + "'";
}
}
// all parameters within valid ranges, so set the state to "Running":
if (string.IsNullOrEmpty(limitReason)) {
_status.ServiceSuspended = false;
if (!string.IsNullOrEmpty(_status.LimitReason)) {
_status.LimitReason = ""; // reset to force a message on next service suspend
writeLog("Service resuming operation (all parameters in valid ranges).");
}
return;
}
// set state to "Running" if no-one is logged on:
if (NoUserIsLoggedOn()) {
_status.ServiceSuspended = false;
if (!string.IsNullOrEmpty(_status.LimitReason)) {
_status.LimitReason = ""; // reset to force a message on next service suspend
writeLog("Service resuming operation (no user logged on).");
}
return;
}
// by reaching this point we know the service should be suspended:
_status.ServiceSuspended = true;
if (limitReason == _status.LimitReason)
return;
writeLog("Service suspended due to limitiations [" + limitReason + "].");
_status.LimitReason = limitReason;
}
/// <summary>
/// Do the main tasks of the service, check system state, trigger transfers, ...
/// </summary>
public void RunMainTasks() {
// mandatory tasks, run on every call:
CheckLogSize();
CheckFreeDiskSpace();
UpdateServiceState();
var delta = DateTime.Now - _lastUserDirCheck;
if (delta.Seconds >= 120)
CreateIncomingDirectories();
// tasks depending on the service state:
if (_status.ServiceSuspended) {
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
// make sure to pause any running transfer:
PauseTransfer();
} else {
// always check the incoming dirs, independently of running transfers:
CheckIncomingDirectories();
// now trigger potential transfer tasks:
CheckAllTransferTasks();
}
}
/// <summary>
/// Helper method to create timestamp strings in a consistent fashion.
/// </summary>
/// <returns>A timestamp string of the current time.</returns>
private static string CreateTimestamp() {
return DateTime.Now.ToString("yyyy-MM-dd__HH-mm-ss");
}
#endregion
#region ActiveDirectory, email address, user name, ...
/// <summary>
/// Check if a user is currently logged into Windows.
///
/// WARNING: this DOES NOT ACCOUNT for users logged in via RDP!!
/// </summary>
/// See https://stackoverflow.com/questions/5218778/ for the RDP problem.
private bool NoUserIsLoggedOn() {
var username = "";
try {
var searcher = new ManagementObjectSearcher("SELECT UserName " +
"FROM Win32_ComputerSystem");
var collection = searcher.Get();
username = (string) collection.Cast<ManagementBaseObject>().First()["UserName"];
}
catch (Exception ex) {
writeLog("Error in getCurrentUsername(): " + ex.Message, true);
}
return username == "";
}
/// <summary>
/// Get the user email address from ActiveDirectory.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>Email address of AD user, an empty string if not found.</returns>
public string GetEmailAddress(string username) {
try {
using (var pctx = new PrincipalContext(ContextType.Domain)) {
using (var up = UserPrincipal.FindByIdentity(pctx, username)) {
if (up != null && !String.IsNullOrEmpty(up.EmailAddress)) {
return up.EmailAddress;
}
}
}
}
catch (Exception ex) {
writeLog("Can't find email address for " + username + ": " + ex.Message);
}
return "";
}
/// <summary>
/// Get the full user name (human-friendly) from ActiveDirectory.
/// </summary>
/// <param name="username">The username.</param>
/// <returns>A human-friendly string representation of the user principal.</returns>
public string GetFullUserName(string username) {
try {
using (var pctx = new PrincipalContext(ContextType.Domain)) {
using (var up = UserPrincipal.FindByIdentity(pctx, username)) {
if (up != null) return up.GivenName + " " + up.Surname;
}
}
}
catch (Exception ex) {
writeLog("Can't find full name for " + username + ": " + ex.Message);
}
return "";
}
#endregion
#region transfer tasks
/// <summary>
/// Helper method to generate the full path of the current temp directory.
/// </summary>
/// <returns>A string with the path to the last tmp dir.</returns>
private string ExpandCurrentTargetTmp() {
return Path.Combine(_config.DestinationDirectory,
_config.TmpTransferDir,
_status.CurrentTargetTmp);
}
/// <summary>
/// Assemble the transfer destination path and check if it exists.
/// </summary>
/// <param name="dirName">The target directory to be checked on the destination.</param>
/// <returns>The full path if it exists, an empty string otherwise.</returns>
private string DestinationPath(string dirName) {
var destPath = Path.Combine(_config.DestinationDirectory, dirName);
if (Directory.Exists(destPath))
return destPath;
return "";
}
/// <summary>
/// Check for transfers to be finished, resumed or newly initiated.
/// </summary>
public void CheckAllTransferTasks() {
// only proceed when in a valid state:
if (_transferState != TxState.Stopped &&
_transferState != TxState.Paused)
return;
// if we're paused, resume the transfer and DO NOTHING ELSE:
if (_transferState == TxState.Paused) {
ResumeTransfer();
return;
}
// first check if there are finished transfers and clean them up:
CheckFinishedTransfers();
// next check if there is a transfer that has to be resumed:
CheckTransfersToResume();
// check if any of the above calls changed the transfer state:
if (_transferState != TxState.Stopped)
return;
// select next directory from "processing" for transfer:
var processingDir = Path.Combine(_managedPath, "PROCESSING");
var queued = new DirectoryInfo(processingDir).GetDirectories();
if (queued.Length == 0)
return;
try {
_status.CurrentTransferSrc = queued[0].GetDirectories()[0].FullName;
Niko Ehrenfeuchter
committed
_status.CurrentTransferSize = GetDirectorySize(_status.CurrentTransferSrc);
}
catch (Exception ex) {
writeLog("Error checking for data to be transferred: " + ex.Message);
throw;
}
StartTransfer();
}
/// <summary>
/// Check the incoming directories for files, move them to the processing location.
/// </summary>
private void CheckIncomingDirectories() {
// iterate over all user-subdirectories:
foreach (var userDir in new DirectoryInfo(_incomingPath).GetDirectories()) {
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
if (IncomingDirIsEmpty(userDir))
continue;
writeLog("Found new files in " + userDir.FullName);
MoveToManagedLocation(userDir);
}
}
/// <summary>
/// Check if a transfer needs to be completed by moving its data from the target temp dir
/// to the final (user) destination and by locally moving the transferred folders to the
/// grace location for deferred deletion.
/// </summary>
private void CheckFinishedTransfers() {
// NOTE: this is intentionally triggered by the timer only to make sure the cleanup
// only happens while all system parameters are within their valid ranges
// make sure the service is in an expected state before cleaning up:
if (_transferState != TxState.Stopped || _status.FilecopyFinished != true)
return;
if (_status.CurrentTargetTmp.Length > 0) {
writeLogDebug("Finalizing transfer, cleaning up target storage location...");
var finalDst = DestinationPath(_status.CurrentTargetTmp);
if (!string.IsNullOrWhiteSpace(finalDst)) {
if (MoveAllSubDirs(new DirectoryInfo(ExpandCurrentTargetTmp()), finalDst)) {
_status.CurrentTargetTmp = "";
}
}
}
if (_status.CurrentTransferSrc.Length > 0) {
writeLogDebug("Finalizing transfer, moving local data to grace location...");
MoveToGraceLocation();
SendTransferCompletedMail();
_status.CurrentTransferSrc = ""; // cleanup completed, so reset CurrentTransferSrc
Niko Ehrenfeuchter
committed
_status.CurrentTransferSize = 0;
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
_transferredFiles.Clear(); // empty the list of transferred files
}
}
/// <summary>
/// Check if conditions for resuming a transfer are met and do so if true.
/// </summary>
private void CheckTransfersToResume() {
// CONDITIONS (a transfer has to be resumed):
// - CurrentTargetTmp has to be non-empty
// - TransferState has to be "Stopped"
// - FileCopyFinished must be false
if (_status.CurrentTargetTmp.Length <= 0 ||
_transferState != TxState.Stopped ||
_status.FilecopyFinished)
return;
writeLogDebug("Resuming transfer from '" + _status.CurrentTransferSrc +
"' to '" + ExpandCurrentTargetTmp() + "'");
StartTransfer();
}
#endregion
#region local filesystem tasks
/// <summary>
/// Check if a given directory is empty. If a marker file is set in the config a
/// file with this name will be created inside the given directory and will be
/// skipped itself when checking for files and directories.
/// </summary>
/// <param name="dirInfo">The directory to check.</param>
/// <returns>True if access is denied or the dir is empty, false otherwise.</returns>
private bool IncomingDirIsEmpty(DirectoryInfo dirInfo) {
try {
var filesInTree = dirInfo.GetFiles("*", SearchOption.AllDirectories);
if (string.IsNullOrEmpty(_config.MarkerFile))
return filesInTree.Length == 0;
// check if there is ONLY the marker file:
if (filesInTree.Length == 1 &&
filesInTree[0].Name.Equals(_config.MarkerFile))
return true;
// make sure the marker file is there:
var markerFilePath = Path.Combine(dirInfo.FullName, _config.MarkerFile);
if (! File.Exists(markerFilePath))
File.Create(markerFilePath);
return filesInTree.Length == 0;
}
catch (Exception e) {
writeLog("Error accessing directories: " + e.Message);
}
// if nothing triggered before, we pretend the dir is empty:
return true;
}
/// <summary>
/// Collect individual files in a user dir in a specific sub-directory. If a marker
/// file is set in the configuration, this will be skipped in the checks.
/// </summary>
/// <param name="userDir">The user directory to check for individual files.</param>
private void CollectOrphanedFiles(DirectoryInfo userDir) {
var fileList = userDir.GetFiles();
var orphanedDir = Path.Combine(userDir.FullName, "orphaned");
try {
if (fileList.Length > 1 ||
(string.IsNullOrEmpty(_config.MarkerFile) && fileList.Length > 0)) {
if (Directory.Exists(orphanedDir)) {
writeLog("Orphaned directory already exists, skipping individual files.");
return;
}
writeLogDebug("Found individual files, collecting them in 'orphaned' folder.");
CreateNewDirectory(orphanedDir, false);
}
foreach (var file in fileList) {
if (file.Name.Equals(_config.MarkerFile))
continue;
writeLogDebug("Collecting orphan: " + file.Name);
file.MoveTo(Path.Combine(orphanedDir, file.Name));
}
}
catch (Exception ex) {
writeLog("Error collecting orphaned files: " + ex.Message + ex.StackTrace);
}
}
/// <summary>
/// Check the incoming directory for files and directories, move them over
/// to the "processing" location (a sub-directory of ManagedDirectory).
/// </summary>
private void MoveToManagedLocation(DirectoryInfo userDir) {
string errMsg;
try {
// first check for individual files and collect them:
CollectOrphanedFiles(userDir);
// the default subdir inside the managed directory, where folders will be
// picked up later by the actual transfer method:
var target = "PROCESSING";
// if the user has no directory on the destination move to UNMATCHED instead:
if (string.IsNullOrWhiteSpace(DestinationPath(userDir.Name))) {
writeLog("Found unmatched incoming dir: " + userDir.Name, true);
target = "UNMATCHED";
}
// now everything that is supposed to be transferred is in a folder,
// for example: D:\ATX\PROCESSING\2017-04-02__12-34-56\user00
var targetDir = Path.Combine(
_managedPath,
CreateTimestamp(),
userDir.Name);
if (MoveAllSubDirs(userDir, targetDir))
return;
errMsg = "unable to move " + userDir.FullName;
}
catch (Exception ex) {
errMsg = ex.Message;
}
writeLog("MoveToManagedLocation(" + userDir.FullName + ") failed: " + errMsg, true);
}
/// <summary>
/// Move transferred files to the grace location for deferred deletion. Data is placed in
/// a subdirectory with the current date and time as its name to denote the timepoint
/// when the grace period for this data starts.
/// </summary>
public void MoveToGraceLocation() {
string errMsg;
// CurrentTransferSrc is e.g. D:\ATX\PROCESSING\2017-04-02__12-34-56\user00
var sourceDirectory = new DirectoryInfo(_status.CurrentTransferSrc);
var dstPath = Path.Combine(
_managedPath,
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
"DONE",
sourceDirectory.Name, // the username directory
CreateTimestamp());
// writeLogDebug("MoveToGraceLocation: src(" + sourceDirectory.FullName + ") dst(" + dstPath + ")");
try {
if (MoveAllSubDirs(sourceDirectory, dstPath)) {
// clean up the processing location:
sourceDirectory.Delete();
if (sourceDirectory.Parent != null)
sourceDirectory.Parent.Delete();
return;
}
errMsg = "unable to move " + sourceDirectory.FullName;
}
catch (Exception ex) {
errMsg = ex.Message;
}
writeLog("MoveToGraceLocation() failed: " + errMsg, true);
}
/// <summary>
/// Move all subdirectories of a given path into a destination directory. The destination
/// will be created if it doesn't exist yet. If a subdirectory of the same name already
/// exists in the destination, a timestamp-suffix is added to the new one.
/// </summary>
/// <param name="sourceDir">The source path as DirectoryInfo object.</param>
/// <param name="destPath">The destination path as a string.</param>
/// <returns>True on success, false otherwise.</returns>
private bool MoveAllSubDirs(DirectoryInfo sourceDir, string destPath) {
// TODO: check whether _transferState should be adjusted while moving dirs!
writeLogDebug("MoveAllSubDirs: " + sourceDir.FullName + " to " + destPath);
try {
// make sure the target directory that should hold all subdirectories to
// be moved is existing:
if (string.IsNullOrEmpty(CreateNewDirectory(destPath, false)))
return false;
foreach (var subDir in sourceDir.GetDirectories()) {
var target = Path.Combine(destPath, subDir.Name);
// make sure NOT to overwrite the subdirectories:
if (Directory.Exists(target))
target += "_" + CreateTimestamp();
writeLogDebug(" - " + subDir.Name + " > " + target);
subDir.MoveTo(target);
}
}
catch (Exception ex) {
writeLog("Error moving directories: " + ex.Message + "\n" +
sourceDir.FullName + "\n" +
destPath, true);
return false;
}
return true;
}
/// <summary>
/// Create a directory with the given name if it doesn't exist yet, otherwise
/// (optionally) create a new one using a date suffix to distinguish it from
/// the existing one.
/// </summary>
/// <param name="dirPath">The full path of the directory to be created.</param>
/// <param name="unique">Add a time-suffix to the name if the directory exists.</param>
/// <returns>The name of the (created or pre-existing) directory. This will only
/// differ from the input parameter "dirPath" if the "unique" parameter is set
/// to true (then it will give the newly generated name) or if an error occured
/// (in which case it will return an empty string).</returns>
private string CreateNewDirectory(string dirPath, bool unique) {
try {
if (Directory.Exists(dirPath)) {
// if unique was not requested, return the name of the existing dir:
if (unique == false)
return dirPath;
dirPath = dirPath + "_" + CreateTimestamp();
}
Directory.CreateDirectory(dirPath);
writeLogDebug("Created directory: " + dirPath);
return dirPath;
}
catch (Exception ex) {
writeLog("Error in CreateNewDirectory(" + dirPath + "): " + ex.Message, true);
}
return "";
}
Niko Ehrenfeuchter
committed
/// <summary>
/// Helper method to check if a directory exists, trying to create it if not.
/// </summary>
/// <param name="path">The full path of the directory to check / create.</param>
/// <returns>True if existing or creation was successful, false otherwise.</returns>
private bool CheckForDirectory(string path) {
if (string.IsNullOrWhiteSpace(path)) {
writeLog("ERROR: CheckForDirectory() parameter must not be empty!");
return false;
}
return CreateNewDirectory(path, false) == path;
}
/// <summary>
/// Ensure the required spooling directories (managed/incoming) exist.
/// </summary>
/// <returns>True if all dirs exist or were created successfully.</returns>
private bool CheckSpoolingDirectories() {
var retval = CheckForDirectory(_incomingPath);
retval &= CheckForDirectory(_managedPath);
retval &= CheckForDirectory(Path.Combine(_managedPath, "PROCESSING"));
retval &= CheckForDirectory(Path.Combine(_managedPath, "DONE"));
retval &= CheckForDirectory(Path.Combine(_managedPath, "UNMATCHED"));
return retval;
}
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
/// <summary>
/// Helper to create directories for all users that have one in the local
/// user directory (C:\Users) AND in the DestinationDirectory.
/// </summary>
private void CreateIncomingDirectories() {
_localUserDirs = new DirectoryInfo(@"C:\Users")
.GetDirectories()
.Select(d => d.Name)
.ToArray();
_remoteUserDirs = new DirectoryInfo(_config.DestinationDirectory)
.GetDirectories()
.Select(d => d.Name)
.ToArray();
foreach (var userDir in _localUserDirs) {
// don't create an incoming directory for the same name as the
// temporary transfer location:
if (_config.TmpTransferDir == userDir)
continue;
// don't create a directory if it doesn't exist on the target:
if (!_remoteUserDirs.Contains(userDir))
continue;
CreateNewDirectory(Path.Combine(_incomingPath, userDir), false);
}
_lastUserDirCheck = DateTime.Now;
}
Niko Ehrenfeuchter
committed
/// <summary>
/// Recursively sum up size of all files under a given path.
/// </summary>
/// <param name="path"></param>
/// <returns>The total size in bytes.</returns>
public static long GetDirectorySize(string path) {
return new DirectoryInfo(path)
.GetFiles("*", SearchOption.AllDirectories)
.Sum(file => file.Length);
}
#endregion
}
}