X-Git-Url: https://code.grnet.gr/git/pithos-ms-client/blobdiff_plain/a0dcfcc90503dcecbd0d0a649024b8b9e4699b5d..025046f11d129cd11d7127519f90a52ed3d68504:/trunk/Pithos.Core/Agents/StatusAgent.cs diff --git a/trunk/Pithos.Core/Agents/StatusAgent.cs b/trunk/Pithos.Core/Agents/StatusAgent.cs index de7af7b..89cce58 100644 --- a/trunk/Pithos.Core/Agents/StatusAgent.cs +++ b/trunk/Pithos.Core/Agents/StatusAgent.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.Contracts; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; using Castle.ActiveRecord; @@ -40,7 +41,31 @@ namespace Pithos.Core.Agents if (!File.Exists(Path.Combine(_pithosDataPath ,"pithos.db"))) ActiveRecordStarter.CreateSchema(); - } + + CreateTrigger(); + + } + + private void CreateTrigger() + { + using (var connection = GetConnection()) + using (var triggerCommand = connection.CreateCommand()) + { + var cmdText = new StringBuilder() + .AppendLine("CREATE TRIGGER IF NOT EXISTS update_last_modified UPDATE ON FileState FOR EACH ROW") + .AppendLine("BEGIN") + .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=old.Id;") + .AppendLine("END;") + .AppendLine("CREATE TRIGGER IF NOT EXISTS insert_last_modified INSERT ON FileState FOR EACH ROW") + .AppendLine("BEGIN") + .AppendLine("UPDATE FileState SET Modified=datetime('now') WHERE Id=new.Id;") + .AppendLine("END;") + .ToString(); + triggerCommand.CommandText = cmdText; + triggerCommand.ExecuteNonQuery(); + } + } + private static InPlaceConfigurationSource GetConfiguration(string pithosDbPath) { @@ -61,7 +86,7 @@ namespace Pithos.Core.Agents }, }; - var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3", pithosDbPath); + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N", pithosDbPath); properties.Add("connection.connection_string", connectionString); var source = new InPlaceConfigurationSource(); @@ -140,14 +165,14 @@ namespace Pithos.Core.Agents var pairs = currentFiles.Union(deletedFiles); - Parallel.ForEach(pairs, pair => + foreach(var pair in pairs) { var fileState = pair.State; var file = pair.File; if (fileState == null) { //This is a new file - var fullPath = pair.File.FullName.ToLower(); + var fullPath = pair.File.FullName; var createState = FileState.CreateForAsync(fullPath, BlockSize, BlockHash); createState.ContinueWith(state => _persistenceAgent.Post(state.Result.Create)); } @@ -156,7 +181,7 @@ namespace Pithos.Core.Agents //This file was deleted while we were down. We should mark it as deleted //We have to go through UpdateStatus here because the state object we are using //was created by a different ORM session. - FileState.UpdateStatus(fileState.Id,FileStatus.Deleted); + _persistenceAgent.Post(()=> UpdateStatusDirect(fileState.Id, FileStatus.Deleted)); } else { @@ -165,14 +190,110 @@ namespace Pithos.Core.Agents //If the hashes don't match the file was changed if (fileState.Checksum != hashString) { - FileState.UpdateStatus(fileState.Id, FileStatus.Modified); + _persistenceAgent.Post(() => UpdateStatusDirect(fileState.Id, FileStatus.Modified)); } } - }); + }; } - + private int UpdateStatusDirect(Guid id, FileStatus status) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + 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; + } + + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int UpdateStatusDirect(string path, FileStatus status) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + try + { + + + using (var connection = GetConnection()) + using ( + var command = + new SQLiteCommand("update FileState set FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE", + connection)) + { + + + command.Parameters.AddWithValue("fileStatus", status); + + command.Parameters.AddWithValue("path", path); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int UpdateStatusDirect(string absolutePath, FileStatus fileStatus, FileOverlayStatus overlayStatus) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("UpdateStatusDirect")) + { + + try + { + + + using (var connection = GetConnection()) + using ( + var command = + new SQLiteCommand( + "update FileState set OverlayStatus= :overlayStatus, FileStatus= :fileStatus where FilePath = :path COLLATE NOCASE ", + connection)) + { + + command.Parameters.AddWithValue("path", absolutePath); + command.Parameters.AddWithValue("fileStatus", fileStatus); + command.Parameters.AddWithValue("overlayStatus", overlayStatus); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + public string BlockHash { get; set; } @@ -210,7 +331,7 @@ namespace Pithos.Core.Agents private readonly string _pithosDataPath; - public FileOverlayStatus GetFileOverlayStatus(string path) + public FileState GetStateByFilePath(string path) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); @@ -220,11 +341,84 @@ namespace Pithos.Core.Agents 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 COLLATE NOCASE", connection)) + { + + command.Parameters.AddWithValue("path", path); + + 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; + } + } - var status = from state in FileState.Queryable - where state.FilePath ==path.ToLower() - select state.OverlayStatus; - return status.Any()? status.First():FileOverlayStatus.Unversioned; + public FileOverlayStatus GetFileOverlayStatus(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 OverlayStatus from FileState where FilePath=:path COLLATE NOCASE", connection)) + { + + command.Parameters.AddWithValue("path", path); + + var s = command.ExecuteScalar(); + return (FileOverlayStatus) Convert.ToInt32(s); + } } catch (Exception exc) { @@ -233,6 +427,25 @@ namespace Pithos.Core.Agents } } + private string GetConnectionString() + { + var connectionString = String.Format(@"Data Source={0}\pithos.db;Version=3;Enlist=N;Pooling=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)) @@ -241,7 +454,7 @@ namespace Pithos.Core.Agents throw new ArgumentException("The path must be rooted","path"); Contract.EndContractBlock(); - _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path.ToLower(),overlayStatus)); + _persistenceAgent.Post(() => FileState.StoreOverlayStatus(path,overlayStatus)); } /* public void RenameFileOverlayStatus(string oldPath, string newPath) @@ -270,9 +483,10 @@ namespace Pithos.Core.Agents Debug.Assert(!path.Contains(FolderConstants.CacheFolder)); Debug.Assert(!path.EndsWith(".ignore")); - _persistenceAgent.Post(() => FileState.UpdateStatus(path.ToLower(), fileStatus, overlayStatus)); + _persistenceAgent.Post(() => UpdateStatusDirect(path, fileStatus, overlayStatus)); } +/* public void StoreInfo(string path,ObjectInfo objectInfo) { if (String.IsNullOrWhiteSpace(path)) @@ -311,17 +525,80 @@ namespace Pithos.Core.Agents }); } - +*/ - public void SetFileStatus(string path, FileStatus status) + public void StoreInfo(string path, ObjectInfo objectInfo) { if (String.IsNullOrWhiteSpace(path)) throw new ArgumentNullException("path"); if (!Path.IsPathRooted(path)) throw new ArgumentException("The path must be rooted", "path"); + if (objectInfo == null) + throw new ArgumentNullException("objectInfo", "objectInfo can't be empty"); Contract.EndContractBlock(); - _persistenceAgent.Post(() => FileState.UpdateStatus(path.ToLower(), status)); + _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 COLLATE NOCASE "; + 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); + 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 COLLATE NOCASE", connection)) + { + command.Parameters.AddWithValue("path", filePath); + var result = command.ExecuteScalar(); + return ((long)result >= 1); + } + + } + + public void SetFileStatus(string path, FileStatus status) + { + if (String.IsNullOrWhiteSpace(path)) + throw new ArgumentNullException("path"); + if (!Path.IsPathRooted(path)) + throw new ArgumentException("The path must be rooted", "path"); + Contract.EndContractBlock(); + + _persistenceAgent.Post(() => UpdateStatusDirect(path, status)); } public FileStatus GetFileStatus(string path) @@ -332,12 +609,23 @@ namespace Pithos.Core.Agents throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - var status = from r in FileState.Queryable - where r.FilePath == path.ToLower() - select r.FileStatus; - return status.Any()?status.First(): FileStatus.Missing; + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("select FileStatus from FileState where FilePath=:path COLLATE NOCASE", connection); + command.Parameters.AddWithValue("path", path); + + var statusValue = command.ExecuteScalar(); + if (statusValue==null) + return FileStatus.Missing; + return (FileStatus)Convert.ToInt32(statusValue); + } } + /// + /// Deletes the status of the specified file + /// + /// public void ClearFileStatus(string path) { if (String.IsNullOrWhiteSpace(path)) @@ -345,8 +633,91 @@ namespace Pithos.Core.Agents if (!Path.IsPathRooted(path)) throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - - _persistenceAgent.Post(() => FileState.DeleteByFilePath(path)); + + _persistenceAgent.Post(() => DeleteDirect(path)); + } + + /// + /// Deletes the status of the specified folder and all its contents + /// + /// + public void ClearFolderStatus(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(); + + _persistenceAgent.Post(() => DeleteFolderDirect(path)); + } + + public IEnumerable GetChildren(FileState fileState) + { + if (fileState == null) + throw new ArgumentNullException("fileState"); + Contract.EndContractBlock(); + + var children = from state in FileState.Queryable + where state.FilePath.StartsWith(fileState.FilePath + "\\") + select state; + return children; + } + + private int DeleteDirect(string filePath) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) + { + + try + { + + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("delete from FileState where FilePath = :path COLLATE NOCASE", + connection); + + command.Parameters.AddWithValue("path", filePath); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } + } + + private int DeleteFolderDirect(string filePath) + { + using (log4net.ThreadContext.Stacks["StatusAgent"].Push("DeleteDirect")) + { + + try + { + + + using (var connection = GetConnection()) + { + var command = new SQLiteCommand("delete from FileState where FilePath = :path or FilePath like :path + '/%' COLLATE NOCASE", + connection); + + command.Parameters.AddWithValue("path", filePath); + + var affected = command.ExecuteNonQuery(); + return affected; + } + } + catch (Exception exc) + { + Log.Error(exc.ToString()); + throw; + } + } } public void UpdateFileChecksum(string path, string checksum) @@ -357,7 +728,7 @@ namespace Pithos.Core.Agents throw new ArgumentException("The path must be rooted", "path"); Contract.EndContractBlock(); - _persistenceAgent.Post(() => FileState.UpdateChecksum(path.ToLower(), checksum)); + _persistenceAgent.Post(() => FileState.UpdateChecksum(path, checksum)); } }