642 |
642 |
}
|
643 |
643 |
}
|
644 |
644 |
|
|
645 |
internal class ObjectInfoComparer:EqualityComparer<ObjectInfo>
|
|
646 |
{
|
|
647 |
public override bool Equals(ObjectInfo x, ObjectInfo y)
|
|
648 |
{
|
|
649 |
return (x.Account == y.Account &&
|
|
650 |
x.Container == y.Container &&
|
|
651 |
x.Name == y.Name);
|
|
652 |
}
|
|
653 |
|
|
654 |
public override int GetHashCode(ObjectInfo obj)
|
|
655 |
{
|
|
656 |
return String.Join("/",obj.Account,obj.Container,obj.Name).GetHashCode();
|
|
657 |
}
|
|
658 |
}
|
|
659 |
|
|
660 |
Dictionary<string, List<ObjectInfo>> _currentSnapshot = new Dictionary<string, List<ObjectInfo>>();
|
|
661 |
Dictionary<string, List<ObjectInfo>> _previousSnapshot = new Dictionary<string, List<ObjectInfo>>();
|
|
662 |
|
645 |
663 |
/// <summary>
|
646 |
664 |
/// Deletes local files that are not found in the list of cloud files
|
647 |
665 |
/// </summary>
|
... | ... | |
658 |
676 |
throw new ArgumentNullException("cloudFiles");
|
659 |
677 |
Contract.EndContractBlock();
|
660 |
678 |
|
661 |
|
//Check the Modified date to ensure that were just created and haven't been uploaded yet
|
662 |
|
//NOTE: The NHibernate LINQ provider doesn't support custom functions so we need to break the query
|
663 |
|
//in two steps
|
664 |
|
//NOTE: DON'T return files that are already in conflict. The first poll would mark them as
|
665 |
|
//"In Conflict" but subsequent polls would delete them
|
666 |
|
/* var t=FileState.Find(new Guid("{cd664c9a-5f17-47c9-b27f-3bcbcb0595ff}"));
|
|
679 |
if (_previousSnapshot.ContainsKey(accountInfo.UserName) && _currentSnapshot.ContainsKey(accountInfo.UserName))
|
|
680 |
_previousSnapshot[accountInfo.UserName] = _currentSnapshot[accountInfo.UserName] ?? new List<ObjectInfo>();
|
|
681 |
else
|
|
682 |
{
|
|
683 |
_previousSnapshot[accountInfo.UserName]=new List<ObjectInfo>();
|
|
684 |
}
|
667 |
685 |
|
668 |
|
var d0 = FileState.Queryable
|
669 |
|
.Where(state =>
|
670 |
|
state.FilePath.StartsWith(accountInfo.AccountPath)).ToList();
|
671 |
|
|
672 |
|
var d1 = FileState.Queryable
|
673 |
|
.Where(state => state.Modified <= pollTime).ToList();
|
674 |
|
var d2= FileState.Queryable
|
675 |
|
.Where(state => state.Modified <= pollTime
|
676 |
|
&&
|
677 |
|
state.FilePath.StartsWith(accountInfo.AccountPath)).ToList();*/
|
678 |
|
|
679 |
|
//Consider for deleteion only files modified before the PREVIOUS poll
|
680 |
|
//A user may perform a file creation or rename at roughly the same time as a poll. In such a case
|
681 |
|
//the new file will appear as deleted
|
682 |
|
var previousPollTime = pollTime.Subtract(TimeSpan.FromMilliseconds(Settings.PollingInterval));
|
683 |
|
|
684 |
|
//Only consider files that are not being modified, ie they are in the Unchanged state
|
685 |
|
var deleteCandidates = FileState.Queryable.Where(state =>
|
686 |
|
state.Modified <= previousPollTime
|
687 |
|
&& state.FilePath.StartsWith(accountInfo.AccountPath)
|
688 |
|
&& state.FileStatus == FileStatus.Unchanged).ToList();
|
689 |
|
|
690 |
|
//TODO: filesToDelete must take into account the Others container
|
691 |
|
var filesToDelete = (from deleteCandidate in deleteCandidates
|
692 |
|
let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath)
|
693 |
|
let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath)
|
694 |
|
let agentActions = _agent.GetEnumerable()
|
695 |
|
where
|
696 |
|
!_lastSeen.ContainsKey(localFile.FullName)
|
697 |
|
&& !cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath )
|
698 |
|
//Exclude files enqueued for uploading
|
699 |
|
//Large files will not appear on the server for multiple polls. They must not be marked as deleted
|
700 |
|
&& !agentActions.Any(action => action.LocalFile.WithProperCapitalization().FullName == localFile.FullName)
|
701 |
|
//Do NOT delete files modified since the previous poll
|
702 |
|
&& localFile.LastAccessTime < previousPollTime
|
703 |
|
select localFile).ToList();
|
704 |
|
|
|
686 |
_currentSnapshot[accountInfo.UserName] = cloudFiles.ToList();
|
|
687 |
|
|
688 |
var deletedObjects = _previousSnapshot[accountInfo.UserName].Except(_currentSnapshot[accountInfo.UserName], new ObjectInfoComparer()).ToList();
|
705 |
689 |
|
|
690 |
|
706 |
691 |
//On the first run
|
707 |
692 |
if (_firstPoll)
|
708 |
693 |
{
|
|
694 |
//Only consider files that are not being modified, ie they are in the Unchanged state
|
|
695 |
var deleteCandidates = FileState.Queryable.Where(state =>
|
|
696 |
state.FilePath.StartsWith(accountInfo.AccountPath)
|
|
697 |
&& state.FileStatus == FileStatus.Unchanged).ToList();
|
|
698 |
|
|
699 |
|
|
700 |
//TODO: filesToDelete must take into account the Others container
|
|
701 |
var filesToDelete = (from deleteCandidate in deleteCandidates
|
|
702 |
let localFile = FileInfoExtensions.FromPath(deleteCandidate.FilePath)
|
|
703 |
let relativeFilePath = localFile.AsRelativeTo(accountInfo.AccountPath)
|
|
704 |
where
|
|
705 |
!cloudFiles.Any(r => r.RelativeUrlToFilePath(accountInfo.UserName) == relativeFilePath)
|
|
706 |
select localFile).ToList();
|
|
707 |
|
|
708 |
|
|
709 |
|
709 |
710 |
//Set the status of missing files to Conflict
|
710 |
711 |
foreach (var item in filesToDelete)
|
711 |
712 |
{
|
... | ... | |
720 |
721 |
}
|
721 |
722 |
UpdateStatus(PithosStatus.HasConflicts);
|
722 |
723 |
StatusNotification.NotifyConflicts(filesToDelete, String.Format("{0} local files are missing from Pithos, possibly because they were deleted",filesToDelete.Count));
|
|
724 |
StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted", filesToDelete.Count), TraceLevel.Info);
|
723 |
725 |
}
|
724 |
726 |
else
|
725 |
727 |
{
|
726 |
|
foreach (var item in filesToDelete)
|
|
728 |
var deletedFiles = new List<FileSystemInfo>();
|
|
729 |
foreach (var objectInfo in deletedObjects)
|
727 |
730 |
{
|
|
731 |
var relativePath = objectInfo.RelativeUrlToFilePath(accountInfo.UserName);
|
|
732 |
var item = GetFileAgent(accountInfo).GetFileSystemInfo(relativePath);
|
728 |
733 |
if (item.Exists)
|
729 |
734 |
{
|
730 |
735 |
//Try to acquire a gate on the file, to take into account files that have been dequeued
|
731 |
736 |
//and are being processed
|
732 |
737 |
//TODO: The gate is not enough. Perhaps we need to keep a journal of processed files and check against
|
733 |
738 |
//that as well.
|
|
739 |
/*
|
734 |
740 |
using (var gate = NetworkGate.Acquire(item.FullName, NetworkOperation.Deleting))
|
735 |
741 |
{
|
736 |
742 |
if (gate.Failed)
|
737 |
743 |
continue;
|
|
744 |
*/
|
738 |
745 |
if ((item.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
|
739 |
746 |
{
|
740 |
747 |
item.Attributes = item.Attributes & ~FileAttributes.ReadOnly;
|
741 |
748 |
|
742 |
749 |
}
|
743 |
750 |
item.Delete();
|
|
751 |
DateTime lastDate;
|
|
752 |
_lastSeen.TryRemove(item.FullName, out lastDate);
|
|
753 |
deletedFiles.Add(item);
|
|
754 |
/*
|
744 |
755 |
}
|
|
756 |
*/
|
745 |
757 |
}
|
746 |
758 |
StatusKeeper.ClearFileStatus(item.FullName);
|
|
759 |
|
747 |
760 |
}
|
748 |
|
StatusNotification.NotifyForFiles(filesToDelete, String.Format("{0} files were deleted",filesToDelete.Count),TraceLevel.Info);
|
|
761 |
StatusNotification.NotifyForFiles(deletedFiles, String.Format("{0} files were deleted", deletedFiles.Count), TraceLevel.Info);
|
749 |
762 |
}
|
750 |
763 |
|
751 |
764 |
}
|
... | ... | |
1236 |
1249 |
|
1237 |
1250 |
}
|
1238 |
1251 |
|
|
1252 |
//Returns true if an action concerns a file that was deleted
|
1239 |
1253 |
private bool IsDeletedFile(CloudAction action)
|
1240 |
|
{
|
|
1254 |
{
|
|
1255 |
//Doesn't work for actions targeting shared files
|
|
1256 |
if (action.IsShared)
|
|
1257 |
return false;
|
1241 |
1258 |
var key = GetFileKey(action.CloudFile);
|
1242 |
1259 |
DateTime entryDate;
|
1243 |
1260 |
if (_deletedFiles.TryGetValue(key, out entryDate))
|