//A separate agent is used to execute delete actions immediatelly;
private Agent<CloudDeleteAction> _deleteAgent;
-
+ ConcurrentDictionary<string,DateTime> _deletedFiles=new ConcurrentDictionary<string, DateTime>();
[System.ComponentModel.Composition.Import]
public IStatusKeeper StatusKeeper { get; set; }
switch (action.Action)
{
case CloudActionType.UploadUnconditional:
- await UploadCloudFile(action);
+ //Abort if the file was deleted before we reached this point
+ if (!IsDeletedFile(action))
+ await UploadCloudFile(action);
break;
case CloudActionType.DownloadUnconditional:
-
- await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
+ if (!IsDeletedFile(action))
+ await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
break;
case CloudActionType.DeleteCloud:
//Redirect deletes to the delete agent
break;
case CloudActionType.RenameCloud:
var moveAction = (CloudMoveAction)action;
- RenameCloudFile(accountInfo, moveAction);
+ if (!IsDeletedFile(action))
+ RenameCloudFile(accountInfo, moveAction);
break;
case CloudActionType.MustSynch:
if (!File.Exists(downloadPath) && !Directory.Exists(downloadPath))
{
- await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
+ if (!IsDeletedFile(action))
+ await DownloadCloudFile(accountInfo, cloudFile, downloadPath);
}
else
{
- await SyncFiles(accountInfo, action);
+ if (!IsDeletedFile(action))
+ await SyncFiles(accountInfo, action);
}
break;
}
//agent
using (var gate = NetworkGate.Acquire(action.LocalFile.FullName, NetworkOperation.Deleting))
{
- // Remove any related actions from the normal agent
- _agent.Remove(queuedAction =>
- queuedAction.CloudFile.Container == action.CloudFile.Container &&
- queuedAction.CloudFile.Name == action.CloudFile.Name);
+
+ //Add the file URL to the deleted files list
+ var key = GetFileKey(action.CloudFile);
+ _deletedFiles[key]=DateTime.Now;
+
// and then delete the file from the server
DeleteCloudFile(accountInfo, cloudFile);
Log.InfoFormat("[ACTION] End Delete {0}:{1}->{2}", action.Action, action.LocalFile,
}
}
+ private static string GetFileKey(ObjectInfo info)
+ {
+ var key = String.Format("{0}/{1}/{2}", info.Account, info.Container,info.Name);
+ return key;
+ }
+
private async Task SyncFiles(AccountInfo accountInfo,CloudAction action)
{
if (accountInfo == null)
continue;
using (new SessionScope(FlushAction.Never))
{
- var state = FileState.FindByFilePath(localFile.FullName);
+ var state = StatusKeeper.GetStateByFilePath(localFile.FullName);
+ //FileState.FindByFilePath(localFile.FullName);
//Common files should be checked on a per-case basis to detect differences, which is newer
yield return new CloudAction(accountInfo, CloudActionType.MustSynch,
if (cloudFile.Name.EndsWith(".ignore", StringComparison.InvariantCultureIgnoreCase))
return;
+
//Are we already downloading or uploading the file?
using (var gate=NetworkGate.Acquire(localPath, NetworkOperation.Downloading))
{
if (fileInfo.Extension.Equals("ignore", StringComparison.InvariantCultureIgnoreCase))
return;
-
+
var relativePath = fileInfo.AsRelativeTo(accountInfo.AccountPath);
if (relativePath.StartsWith(FolderConstants.OthersFolder))
{
}
+ private bool IsDeletedFile(CloudAction action)
+ {
+ var key = GetFileKey(action.CloudFile);
+ DateTime entryDate;
+ if (_deletedFiles.TryGetValue(key, out entryDate))
+ {
+ //If the delete entry was created after this action, abort the action
+ if (entryDate > action.Created)
+ return true;
+ //Otherwise, remove the stale entry
+ _deletedFiles.TryRemove(key, out entryDate);
+ }
+ return false;
+ }
+
private bool HandleUploadWebException(CloudAction action, WebException exc)
{
var response = exc.Response as HttpWebResponse;
ActiveRecordStarter.CreateSchema();
}
+
+
private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath)
{
if (String.IsNullOrWhiteSpace(pithosDbPath))
private int UpdateStatusDirect(Guid id, FileStatus status)
{
- try
+ using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
{
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
- using (var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id ",
- connection))
+ try
{
+ using (var connection = GetConnection())
+ using (
+ var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where Id = :id ",
+ connection))
+ {
+ command.Parameters.AddWithValue("fileStatus", status);
+
+ command.Parameters.AddWithValue("id", id);
+
+ var affected = command.ExecuteNonQuery();
+
+ return affected;
+ }
- command.Parameters.AddWithValue("fileStatus", status);
-
- command.Parameters.AddWithValue("id", id);
- connection.Open();
- var affected = command.ExecuteNonQuery();
- return affected;
+ }
+ catch (Exception exc)
+ {
+ Log.Error(exc.ToString());
+ throw;
}
}
- catch (Exception exc)
- {
- Log.Error(exc.ToString());
- throw;
- }
-
}
private int UpdateStatusDirect(string path, FileStatus status)
{
- try
+ using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
{
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
- using (var command = new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path ",
- connection))
+ try
{
+
+ using (var connection = GetConnection())
+ using (
+ var command =
+ new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path ",
+ connection))
+ {
- command.Parameters.AddWithValue("fileStatus", status);
- command.Parameters.AddWithValue("path", path.ToLower());
- connection.Open();
- var affected = command.ExecuteNonQuery();
- return affected;
+ command.Parameters.AddWithValue("fileStatus", status);
+
+ command.Parameters.AddWithValue("path", path.ToLower());
+
+ var affected = command.ExecuteNonQuery();
+ return affected;
+ }
+ }
+ catch (Exception exc)
+ {
+ Log.Error(exc.ToString());
+ throw;
}
}
- catch (Exception exc)
- {
- Log.Error(exc.ToString());
- throw;
- }
-
}
private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus)
{
- try
+ using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect"))
{
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
- using (var command = new SQLiteCommand("update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ",
- connection))
+ try
{
+
- command.Parameters.AddWithValue("path", absolutePath.ToLower());
- command.Parameters.AddWithValue("fileStatus", fileStatus);
- command.Parameters.AddWithValue("overlayStatus", overlayStatus);
- connection.Open();
- var affected = command.ExecuteNonQuery();
- return affected;
+ using (var connection = GetConnection())
+ using (
+ var command =
+ new SQLiteCommand(
+ "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path ",
+ connection))
+ {
+
+ command.Parameters.AddWithValue("path", absolutePath.ToLower());
+ command.Parameters.AddWithValue("fileStatus", fileStatus);
+ command.Parameters.AddWithValue("overlayStatus", overlayStatus);
+
+ var affected = command.ExecuteNonQuery();
+ return affected;
+ }
+ }
+ catch (Exception exc)
+ {
+ Log.Error(exc.ToString());
+ throw;
}
}
- catch (Exception exc)
- {
- Log.Error(exc.ToString());
- throw;
- }
-
}
private readonly string _pithosDataPath;
+ public FileState GetStateByFilePath(string path)
+ {
+ if (String.IsNullOrWhiteSpace(path))
+ throw new ArgumentNullException("path");
+ if (!Path.IsPathRooted(path))
+ throw new ArgumentException("The path must be rooted", "path");
+ Contract.EndContractBlock();
+
+ try
+ {
+
+ using (var connection = GetConnection())
+ using (var command = new SQLiteCommand("select Id, FilePath, OverlayStatus,FileStatus ,Checksum ,Version ,VersionTimeStamp,IsShared ,SharedBy ,ShareWrite from FileState where FilePath=:path", connection))
+ {
+
+ command.Parameters.AddWithValue("path", path.ToLower());
+
+ using (var reader = command.ExecuteReader())
+ {
+ if (reader.Read())
+ {
+ //var values = new object[reader.FieldCount];
+ //reader.GetValues(values);
+ var state = new FileState
+ {
+ Id = reader.GetGuid(0),
+ FilePath = reader.IsDBNull(1)?"":reader.GetString(1),
+ OverlayStatus =reader.IsDBNull(2)?FileOverlayStatus.Unversioned: (FileOverlayStatus) reader.GetInt64(2),
+ FileStatus = reader.IsDBNull(3)?FileStatus.Missing:(FileStatus) reader.GetInt64(3),
+ Checksum = reader.IsDBNull(4)?"":reader.GetString(4),
+ Version = reader.IsDBNull(5)?default(long):reader.GetInt64(5),
+ VersionTimeStamp = reader.IsDBNull(6)?default(DateTime):reader.GetDateTime(6),
+ IsShared = !reader.IsDBNull(7) && reader.GetBoolean(7),
+ SharedBy = reader.IsDBNull(8)?"":reader.GetString(8),
+ ShareWrite = !reader.IsDBNull(9) && reader.GetBoolean(9)
+ };
+/*
+ var state = new FileState
+ {
+ Id = (Guid) values[0],
+ FilePath = (string) values[1],
+ OverlayStatus = (FileOverlayStatus) (long)values[2],
+ FileStatus = (FileStatus) (long)values[3],
+ Checksum = (string) values[4],
+ Version = (long?) values[5],
+ VersionTimeStamp = (DateTime?) values[6],
+ IsShared = (long)values[7] == 1,
+ SharedBy = (string) values[8],
+ ShareWrite = (long)values[9] == 1
+ };
+*/
+ return state;
+ }
+ else
+ {
+ return null;
+ }
+
+ }
+ }
+ }
+ catch (Exception exc)
+ {
+ Log.ErrorFormat(exc.ToString());
+ throw;
+ }
+ }
+
public FileOverlayStatus GetFileOverlayStatus(string path)
{
if (String.IsNullOrWhiteSpace(path))
try
{
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
+
+ using (var connection = GetConnection())
using (var command = new SQLiteCommand("select OverlayStatus from FileState where FilePath=:path", connection))
{
command.Parameters.AddWithValue("path", path.ToLower());
- connection.Open();
+
var s = command.ExecuteScalar();
return (FileOverlayStatus) Convert.ToInt32(s);
}
private string GetConnectionString()
{
- var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True;Default Isolation Level=ReadCommited", _pithosDataPath);
+ var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=True;Default Isolation Level=ReadCommited;show_sql=true", _pithosDataPath);
return connectionString;
}
+ private SQLiteConnection GetConnection()
+ {
+ var connectionString = GetConnectionString();
+ var connection = new SQLiteConnection(connectionString);
+ connection.Open();
+ using(var cmd =connection.CreateCommand())
+ {
+ cmd.CommandText = "PRAGMA journal_mode=WAL";
+ cmd.ExecuteNonQuery();
+ }
+ return connection;
+ }
+
public void SetFileOverlayStatus(string path, FileOverlayStatus overlayStatus)
{
if (String.IsNullOrWhiteSpace(path))
_persistenceAgent.Post(() => UpdateStatusDirect(path.ToLower(), fileStatus, overlayStatus));
}
+/*
public void StoreInfo(string path,ObjectInfo objectInfo)
{
if (String.IsNullOrWhiteSpace(path))
});
}
- public void StoreInfoDirect(string path, ObjectInfo objectInfo)
+*/
+
+ public void StoreInfo(string path, ObjectInfo objectInfo)
{
if (String.IsNullOrWhiteSpace(path))
throw new ArgumentNullException("path");
throw new ArgumentNullException("objectInfo", "objectInfo can't be empty");
Contract.EndContractBlock();
- _persistenceAgent.Post(() =>
- {
- try
- {
-
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
- {
- if (StateExists(path))
- var command =
- new SQLiteCommand(
- "update FileState set FileStatus= :fileStatus where Id = :id ",
- connection);
-
-
- var filePath = path.ToLower();
- //Load the existing files state and set its properties in one session
- using (new SessionScope())
- {
- //Forgetting to use a sessionscope results in two sessions being created, one by
- //FirstOrDefault and one by Save()
- var state = FileState.FindByFilePath(filePath);
-
- //Create a new empty state object if this is a new file
- state = state ?? new FileState();
-
- state.FilePath = filePath;
- state.Checksum = objectInfo.Hash;
- state.Version = objectInfo.Version;
- state.VersionTimeStamp = objectInfo.VersionTimestamp;
-
- state.FileStatus = FileStatus.Unchanged;
- state.OverlayStatus = FileOverlayStatus.Normal;
-
-
- //Do the save
- state.Save();
- }
-
-
- command.Parameters.AddWithValue("fileStatus", status);
-
- command.Parameters.AddWithValue("id", id);
- connection.Open();
- var affected = command.ExecuteNonQuery();
- return;
- }
- }
- catch (Exception exc)
- {
- Log.Error(exc.ToString());
- throw;
- }
-
- });
+ _persistenceAgent.Post(() => StoreInfoDirect(path, objectInfo));
}
+ private void StoreInfoDirect(string path, ObjectInfo objectInfo)
+ {
+ try
+ {
+
+ using (var connection = GetConnection())
+ using (var command = new SQLiteCommand(connection))
+ {
+ if (StateExists(path, connection))
+ command.CommandText =
+ "update FileState set FileStatus= :fileStatus where FilePath = :path ";
+ else
+ {
+ command.CommandText =
+ "INSERT INTO FileState (Id,FilePath,Checksum,Version,VersionTimeStamp,FileStatus,OverlayStatus) VALUES (:id,:path,:checksum,:version,:versionTimeStamp,:fileStatus,:overlayStatus)";
+ command.Parameters.AddWithValue("id", Guid.NewGuid());
+ }
+
+ command.Parameters.AddWithValue("path", path.ToLower());
+ command.Parameters.AddWithValue("checksum", objectInfo.Hash);
+ command.Parameters.AddWithValue("version", objectInfo.Version);
+ command.Parameters.AddWithValue("versionTimeStamp",
+ objectInfo.VersionTimestamp);
+ command.Parameters.AddWithValue("fileStatus", FileStatus.Unchanged);
+ command.Parameters.AddWithValue("overlayStatus",
+ FileOverlayStatus.Normal);
+
+ var affected = command.ExecuteNonQuery();
+ return;
+ }
+ }
+ catch (Exception exc)
+ {
+ Log.Error(exc.ToString());
+ throw;
+ }
+ }
+
private bool StateExists(string filePath,SQLiteConnection connection)
{
- using(var command=new SQLiteCommand("Select count(*) from FileState where FilePath=:path",connection))
+ using (var command = new SQLiteCommand("Select count(*) from FileState where FilePath=:path", connection))
{
command.Parameters.AddWithValue("path", filePath.ToLower());
- var result=command.ExecuteScalar();
- new SQLiteCommand(
- "update FileState set FileStatus= :fileStatus where Id = :id ",
- connection);
+ var result = command.ExecuteScalar();
+ return ((long)result >= 1);
+ }
-
-
}
public void SetFileStatus(string path, FileStatus status)
throw new ArgumentException("The path must be rooted", "path");
Contract.EndContractBlock();
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
+
+ using (var connection = GetConnection())
{
var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path", connection);
command.Parameters.AddWithValue("path", path.ToLower());
- connection.Open();
+
var s = command.ExecuteScalar();
return (FileStatus)Convert.ToInt32(s);
}
private int DeleteDirect(string filePath)
{
- try
+ using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect"))
{
- var connectionString = GetConnectionString();
- using (var connection = new SQLiteConnection(connectionString))
+ try
{
- var command = new SQLiteCommand("delete FileState where FilePath = :path",
- connection);
- command.Parameters.AddWithValue("path", filePath.ToLower());
- connection.Open();
- var affected = command.ExecuteNonQuery();
- return affected;
+
+ using (var connection = GetConnection())
+ {
+ var command = new SQLiteCommand("delete from FileState where FilePath = :path",
+ connection);
+
+ command.Parameters.AddWithValue("path", filePath.ToLower());
+
+ var affected = command.ExecuteNonQuery();
+ return affected;
+ }
+ }
+ catch (Exception exc)
+ {
+ Log.Error(exc.ToString());
+ throw;
}
}
- catch (Exception exc)
- {
- Log.Error(exc.ToString());
- throw;
- }
-
}
public void UpdateFileChecksum(string path, string checksum)