我正在寻找一种永久存储状态+序列化类(或其他东西)的方法,这样当计算机/程序崩溃时,我可以在下一个程序启动时修复东西。例如,当我上传文件时,我需要在下次启动时知道它是否没有完成。这样我就可以删除远程文件(或者如果可能的话附加到它等等)
现在,我想出了一些伪代码,它基本上使用数据库(通过接口轻松交换)来创建存储状态的表+列+行,并在作业成功完成时删除行。现在,在每次启动时,我们都可以从数据库中读取数据,并根据我们上次所处的状态调用相应的操作。
问题:
- 我不确定我正在做的事情是否有必要完成这个......替代方案?
- 如果这是正确的方法,我有点希望有一个可用的库已经这样做了(无需重新发明轮子)
- 依赖注入呢?在我的代码中,我只是将类名添加到行中,但是如果我想为插件公开所有这些并且存在类名冲突怎么办?事实上,我怎么知道我必须从哪个注入的 dll 开始传递存储的数据?(不确定我此时是否仍然有意义。)或者如果删除了依赖项怎么办,我将如何识别哪些数据库文件(如果使用文件)属于所述删除的插件,并要求用户保留它与否?
到目前为止的示例代码:
class CPersistantthingy
{
IDatabase mDatabase;
string _mTablename;
List<string> _mCols = new List<string>(new string[]{"State","SerializedClass","ClassType"}); //can't contain "ID"
public Dictionary<string,string> AcquireRow()
{
//Make sure table exists and has the columns we need
CreateTableIfNotExist();
if(!VerifyTableHasColumns())
throw new CorruptedTableException();
//Create new row with State "undef", fetch the Auto Increment ID and return it
return mDatabase.CreateRow();
}
public void WriteRow(Dictionary<string,string> row)
{
mDatabase.UpdateRow(row); //The database will lookup by ID and update fields.
}
private void CreateTableIfNotExist()
{
mDatabase.CreateTableIfNotExist(_mTablename, _mCols);
}
private bool VerifyTableHasColumns()
{
return mDatabase.VerifyTableHasColumns(_mTablename, _mCols);
}
}
interface IDatabase
{
//CreateTable / CreateRow / UpdateRow etc etc
}
文件上传示例:
States:
Acquired //Chosen a remote path
Started //First bytes written to server
Failed //Upload failed
所以一行看起来像:
State: Acquired|Started|Failed
SerializedClass: {host: "127.0.0.1", user: "usr", local:"C:\somefile.txt", remote:"/we/somefile.txt"}
ClassType: CFileObj
所以现在在我的程序中我可以像这样使用它;
SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
private void StartUpload(string localfile)
{
var row = pers.AcquireRow();
row["State"] = "Acquired";
row["ClassType"] = "CFileObj";
/*Imagine that GetRemotePath reserves a remote path on the server we'll
upload our file to. That's how my current system works.*/
string remfile = GetRemotePath(localfile);
CFileObj obj = new CFileObj(currenthost, currentuser, localfile, remfile);
row["SerializedClass"] = obj.Serialize();
//Write the acquired state
pers.WriteRow(row);
//Uploading class has a callback to let us know when the first bytes have been written.
try
{
StartUpload(obj.local, obj.remote, () => { row["State"] = "Started"; pers.WriteRow(row); } );
}
catch(Exception ex)
{
row["State"] = "Failed";
pers.WriteRow(row);
throw; //Catch at caller to immediately fix rather than waiting for next boot.
}
//Now do whatever else you want to do on a succesful upload.
//Maybe add new states for this too so we know at least the upload succeeded.
//Finally delete the entry so it's not picked up at next boot.
pers.DeleteByKey(row["ID"]);
}
然后确保服务器在崩溃后清除失败的文件(不完整的上传):
public static void Main()
{
SomeDatabaseClass_Object db = new SomeDatabaseClass_Object(blabla);
CPersistantthingy pers = new CPersistantthingy(db, "fileuploads");
CUploadServerObj serverObj = new CUploadServerObj(bla,di,bla);
serverObj.Connect();
//Now let's imagine a class that hooks a state to an action etc.
var ima = new CImagineIt(pers);
/*I may have picked a bad example because I'd have to do the same for all States
but you get the idea. */
ima.AddStateAction("Failed", (row) => { FixFailedUpload(serverObj, pers, row); });
//Read all rows from database and fire actions accordingly
ima.DoWork();
}
使用该操作,在这种情况下,只需检查服务器上的文件是否小于本地文件。
private void FixFailedUpload(CUploadServerObj serverObj, CPersistantthingy pers, Dictionary<string,string> row)
{
if(!row["ClassType"].Equals("CFileObj"))
{
//handle error
return;
}
CFileObj obj;
try
{
obj = DeSerialize(row["SerializedClass"]);
}//Catch and handle etc etc
//Are we using different credentials this boot? Then we can't check.
if(obj.host != currenthost || obj.usr != currentuser)
{
//handle error and return
}
try
{
if(serverObj.RemoteSizeSmaller(obj.local, obj.remote))
{
if(serverObj.DeleteFromRemote(obj.remote))
{
pers.DeleteByKey(row["ID"]);
}
}
}
catch(RemoteFileNotExistsException)
{
//The file didn't even exist so no worries
}
catch(RemoteFileDeleteException)
{
//The file existed but could not be removed.. it's probably time to request manual user input now because that's just weird.
}
}
我认为这可行,但并不理想。可能上传失败,因为保留路径已被另一个进程写入。这意味着我们不能使用Acquired
状态来证明删除文件是正当的。事后检查失败原因也无济于事,因为程序可能会在两者之间崩溃。这意味着我们Acquired
无论如何都被困住了。或者,也许我们在写入的第一个字节和它的回调之间崩溃,这意味着Acquired
即使我们确实获得了我们保留的文件路径,我们也被卡住了。