Как защитить файл от повреждения при отключении питания?

360
12 мая 2017, 14:11

Программа хранит ряд настроек в xml-файле. Если в момента записи настройки отключается питание компьютера, содержимое файла пропадает (сам файл остается, но если открыть - он пустой). Появилась идея сохранять изменения во временный файл, а затем переносить их в основной. Тогда, теоретически, должен повредиться только один из файлов при сбросе питания. Но на деле пропадает содержимое обоих файлов. Причина в коде? Или сам подход неправильный? Сохранение временного файла и его копирование в файл-оригинал представлено ниже:

StreamWriter streamWriter = new StreamWriter(FileName_);
BlockFile.WaitOne();
xmlDoc.Save(streamWriter);                    
BlockFile.Release();
streamWriter.Dispose();
File.Copy(FileName_, FileName, true);

В настоящее время код метода выглядит так:

static public void SetValueOfParameter(string nodeName, string parameterName, string valueOfParameter)
{            
    BlockFile.WaitOne();
    XmlDocument xmlDoc = new XmlDocument();
    using (StreamReader streamReader = new StreamReader(FileName))
    {
        xmlDoc.Load(streamReader);
        XmlElement xRoot = xmlDoc.DocumentElement;
        XmlNode node = xRoot.SelectSingleNode(nodeName);
        node.SelectSingleNode(parameterName).InnerText = valueOfParameter;
        streamReader.Close();
        streamReader.Dispose();
    }
    using (StreamWriter streamWriter = new StreamWriter(FileName_))
    {
        xmlDoc.Save(streamWriter);
        streamWriter.Flush();
        streamWriter.Close();
        streamWriter.Dispose();
    }
    FileInfo fInfo = new FileInfo(FileName_);
    if (fInfo.Exists && fInfo.Length>0)
    {
        if (File.Exists(FileName))
        {
            File.Copy(FileName_,FileName,true);
        }
    }
    BlockFile.Release();
}

Вместо File.Copy(FileName_,FileName,true); было испробовано удаление/перемещение. Эффект тот же - два файла пустые. Кэширование диска снято

Answer 1

Судя по описанию проблемы - пропаданию данных уже после того, как они были записаны на диск (а они были записаны, т.к. вы смогли скопировать файл) - у вас включено кэширование записи на диск. Проверить можно в свойствах диска в Device Manager.

Кэширование записи безопасно только на дисках с автономным питанием (рейдах с батарейкой). Именно поэтому оно отключено по умолчанию. Если у вас обычный десктопный диск - данные будут теряться, и из кода на C# вы это никак не поборете.

Answer 2

Рекомендую сделать так:

  1. Вы сохраняете новый файл с именем settings_new.xml
  2. В случае успеха (проверка файла settings_new.xml на существование и ненулевую длину) удаляете файл settings.xml
  3. Переименовываете файл settings_new.xml в settings.xml (можно так же скопировать и удалить оригинал)
  4. При новом запуске приложения сначала ищете файл settings_new.xml. Если он существует и не пуст, значит произошел сбой питания или некорректное завершение работы программы. В этом случае доделываете операции 2 и 3.

Если вы выполните этот алгоритм, то при любом сбое вы будете уверены настройки не пропадут.

Answer 3

Доброго времени суток! Последовательность должна быть такой:

Вариант 1:

Открытие и работа с файлом

1) Проверяете существование ~settings.xml

1.1) Проверяете валидность файла ~settings.xml

2.1) Если существует и валиден то открываете его с доступом на чтение иначе удаляете его и переходите к пункту 3

2.2) Открываете файл settings.xml с доступом на запись

2.3) Записываете содержимое файла ~settings.xml в settings.xml

2.4) Закрываете файлы. Удаляете ~settings.xml (далее работает по пунктам следующим пунктам)

3) Если файл ~settings.xml не существует, то открываете файл settings.xml с доступом на чтение

4) Создаете файл ~settings.xml и открываете с доступом на запись

5) Работаете с файлом ~settings.xml

Сохранение результатов

1) Закрываете оба файла

2) Открываете файл ~settings.xml с доступом на чтение и файл settings.xml с доступом на запись

3) Перезаписываете файл settings.xml содержимым файла ~settings.xml

4) Закрываете оба файла. Удаляете ~settings.xml

Вариант 2:

Открытие и работа с файлом

1) Проверяете существование ~settings.xml

1.1) Проверяете валидность файла ~settings.xml

2.2) Если ~settings.xml существует и валиден то удаляете settings.xml иначе удяляете его и переходите к пункту 3

2.3) Копируете файл ~settings.xml и создаете новый файл с названием settings.xml

2.4) Удаляете ~settings.xml (далее работает по пунктам следующим пунктам)

3) Если файл ~settings.xml не существует, то копируете settings.xml и создаете новый файл с названием ~settings.xml

4) Работаете с файлом ~settings.xml (На этом этапе можно открыть файл ~settings.xml с доступом на запись)

Сохранение результатов

1) Закрываете файл ~settings.xml

2) Удаляете файл settings.xml

3) Копируете файл ~settings.xml и создаете файл с названием settings.xml

4) Удаляете ~settings.xml

Этот алгоритм должен предотвратить полную потерю данных

Answer 4

Могу еще предложить не такой красивый, но гарантированно сохраняющий данные вариант:

  1. Создайте папку settings, сохраняйте файлы туда с timestamp, например 1494856800.xml
  2. При запуске программы ищите наиболее новый файл (максимальный timestamp) с ненулевой длинной.
  3. Удаляете все файлы кроме найденного (эту операцию для надежности можно поменять на "Удаляете все файлы, кроме 10 последних")

UPD: Добавил пример кода:

        static public void SetValueOfParameter(string nodeName, string parameterName, string valueOfParameter)
    {
        // BlockFile.WaitOne();
        string old_filename = get_setting_filename();
        string new_filename = DateTime.Now.ToFileTimeUtc().ToString();
        XmlDocument xmlDoc = new XmlDocument();
        using (StreamReader streamReader = new StreamReader(old_filename))
        {
            xmlDoc.Load(streamReader);
            XmlElement xRoot = xmlDoc.DocumentElement;
            XmlNode node = xRoot.SelectSingleNode(nodeName);
            node.SelectSingleNode(parameterName).InnerText = valueOfParameter;
            streamReader.Close();
            streamReader.Dispose();
        }
        using (StreamWriter streamWriter = new StreamWriter(new_filename))
        {
            xmlDoc.Save(streamWriter);
            streamWriter.Flush();
            streamWriter.Close();
            streamWriter.Dispose();
        }
        remove_old_files();
        // BlockFile.Release();
    }
    static public string get_setting_filename()
    {
        DirectoryInfo d = new DirectoryInfo("settings");
        FileInfo[] Files = d.GetFiles("*.xml");
        DateTime min_date = DateTime.MinValue;
        string filename = "";
        // ищем не пустой файл с максимальной датой
        foreach (FileInfo file in Files)
        {
            // 4 - Чтобы файл не состоял только из символа окончания файла
            if (file.LastWriteTimeUtc > min_date && file.Length > 4)
            {
                min_date = file.LastWriteTimeUtc;
                filename = file.Name;
            }
        }
        return filename;
    }
    static public void remove_old_files()
    {
        DirectoryInfo d = new DirectoryInfo("settings");
        FileInfo[] Files = d.GetFiles("*.xml");
        DateTime min_date = DateTime.MinValue;
        int count_save_files = 10;
        foreach (FileInfo file in Files.OrderBy(f => f.LastWriteTime).Reverse().ToList())
        {
            // Проверяем что файл не пустой
            if (file.Length > 4)
            {
                // Не удаляем файлы настроек за последние сутки, независимо от количества
                if (file.LastWriteTime < DateTime.Now.AddDays(-1))
                {
                    // Сохраняем 10 последних файлов (помимо последних суток)
                    if (count_save_files > 0)
                    {
                        count_save_files--;
                    }
                    else
                    {
                        File.Delete(file.Name);
                    }
                }
            }
            else
            {
                // Удаляем любые поврежденные файлы
                File.Delete(file.Name);
            }
        }
    }
READ ALSO
Как упростить проверку на null на C#?

Как упростить проверку на null на C#?

Есть ли возможность упростить такое выражение:

369
Считывание из XML файла

Считывание из XML файла

Есть XML файл, он лежит в сторонней от программы папкеКак из него считать информацию?

394
Automapper. Многие к одному

Automapper. Многие к одному

Здравствуйте! Есть три источника данных (реляционная ДБ, EF 6, database first)Знаю, как привести к виду один к одному

374