1

我正在寻找一种永久存储状态+序列化类(或其他东西)的方法,这样当计算机/程序崩溃时,我可以在下一个程序启动时修复东西。例如,当我上传文件时,我需要在下次启动时知道它是否没有完成。这样我就可以删除远程文件(或者如果可能的话附加到它等等)

现在,我想出了一些伪代码,它基本上使用数据库(通过接口轻松交换)来创建存储状态的表+列+行,并在作业成功完成时删除行。现在,在每次启动时,我们都可以从数据库中读取数据,并根据我们上次所处的状态调用相应的操作。

问题:

  1. 我不确定我正在做的事情是否有必要完成这个......替代方案?
  2. 如果这是正确的方法,我有点希望有一个可用的库已经这样做了(无需重新发明轮子)
  3. 依赖注入呢?在我的代码中,我只是将类名添加到行中,但是如果我想为插件公开所有这些并且存在类名冲突怎么办?事实上,我怎么知道我必须从哪个注入的 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即使我们确实获得了我们保留的文件路径,我们也被卡住了。

4

0 回答 0