Хранение пользовательских типов данных. Часть 3.1. Хранение в файлах.

Как и было обещано в предыдущей части, начинаем разбор методов хранения данных. Уже с примерами :)

Сначала рассмотрим "файловый" вариант. Для хранения простых данных можно использовать обычные текстовые файлы, откуда все по мере надобности и читать. Если данные можно "сгруппировать", и глубина вложения - всего один уровень, то уже в игру вступают ini-файлы. Если же данные сложноструктурированы, то xml в помощь и масса головной боли по их обработке.

Проблема файлового хранения состоит в месте хранения этих файлов. Предполагаем, что файлы должны меняться, то есть пользователь с правами User гарантированно должен иметь к ним полный доступ. Хранить их в %temp% нереально - это чуть ли не первый каталог, который чистят при проблемах работы AutoCAD. В "Мои документы"? Неконтролируемо появляющиеся файлы в личном каталоге - прекрасный повод для паники :) Для хранения таких файлов придется создавать каталог в %UserProfile% (например, c:\documents and settings\user\Application Data\), и внутри него уже структурировать все хранимые файлы. Я говорю о данных, имеющих отношение только к конкретному пользователю!

В реестре данные хранить тоже можно и, как правило, нужно. Ветка HKEY_CURRENT_USER, как правило, полностью доступна для текущего пользователя. Создается свой подключ в Software и творим там чего хотим :)

Каждый из методов имеет свои плюсы и минусы. Основной плюс файлового подхода - как правило, все видно и видно сразу. И это идеальное средство, если данные должны храниться на сервере. Основной плюс реестра - скорость доступа. Ну и дополнительно - файлы, как правило, пользователи уровня "обезьяна с гранатой" рано или поздно находят, а вот в реестр лезут уже совсем отмороженные товарищи :)

И вот еще... Данные могут быть "общими" для всех профилей AutoCAD - как имеющимися, так и вновь создаваемыми. А могут быть и "индивидуальными". Через файлы подобное решить проблематично, а вот реестровый подход такое позволяет.

Для примера рассмотрим следующую достаточно простую задачу: есть список системных переменных, которые надо устанавливать в любом файле в определенные значения. Для упрощения задачи возьмем, пожалуй, elevation, blipmode, filedia, cmddia, attdia, attreq, insbase. Этого достаточно :)

Сначала сформируем список этих переменных со значениями, которые нравятся разработчику:

1
2
3
4
5
6
7
8
9
(setq *lispru-sysvar-list* '(("elevation" . 0.0)
                             ("blipmode" . 0)
                             ("filedia" . 1)
                             ("cmddia" . 1)
                             ("attdia" . 1)
                             ("attreq" . 1)
                             ("insbase" 0.0 0.0 0.0)
                             )
      ) ;_ end of setq

Код здесь.

Теперь начинается веселье :) Сначала разберем файловый подход. Первым на очереди "простые файлы", не-ini. С них и начнем.

Да вот беда - прежде чем начинать писать код, необходимо все же определиться с местом хранения файлов. В %AppData% создадим свой каталог LispRu, в котором будет еще один подкаталог - Datas, и уже в нем будем хранить все данные.

%AppData% получить просто - (getenv "APPDATA"), а дальше? Создать "одним движением" каталог вида %AppData%\LispRu\Datas средствами LISP невозможно: vl-mkdir не умеет создавать многоуровневые каталоги, а привлекать ради одной-единственной задачи ObjectARX или .NET не очень разумно. Поэтому пишем отдельную функцию, создающую каталог любого уровня вложенности:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
(defun _lispru-dir-create (path)
                          ;|
*    Гарантированное создание каталога.
*    Параметры вызова:
  path  создаваемый каталог
|;

  (setq path (vl-string-right-trim "\" path))
  (cond
    ((vl-file-directory-p path) path)
    (t
     (vl-mkdir
       (strcat (_lispru-dir-create (vl-filename-directory path))
               "
\"
               (vl-filename-base path)
               (cond ((vl-filename-extension path))
                     (t "
")
                     ) ;_ end of cond
               ) ;_ end of strcat
       ) ;_ end of vl-mkdir
     (if (vl-file-directory-p path)
       path
       ) ;_ end of if
     )
    ) ;_ end of cond
  ) ;_ end of defun

Код _lispru-dir-create.lsp.

Теперь уже можно создавать глобальную (или "суперглобальную" ;)) переменную файла с настройками:

Для "не-ini" файла:

1
2
3
4
5
6
(setq *lispru-sysvar-datfile*
       (strcat
         (_lispru-dir-create (strcat (vl-string-right-trim "\" (getenv "APPDATA")) "\\LispRu\\Datas"))
         "
\\sysvar.dat"
         ) ;_ end of strcat
      ) ;_ end of setq

Для "ini"-файла:

1
2
3
4
5
6
(setq *lispru-sysvar-inifile*
       (strcat
         (_lispru-dir-create (strcat (vl-string-right-trim "\" (getenv "APPDATA")) "\\LispRu\\Datas"))
         "
\\sysvar.ini"
         ) ;_ end of strcat
      ) ;_ end of setq

"xml"-файлы рассматривать не будем, там слишком сложный механизм работы с ними. Если есть желание - у себя на блоге я разбирал эти моменты.

Дальше надо подключить фантазию и подумать: допустим, файл dwg открывается или создается. Значит, эти системные переменные должны получить устанавливаемые значения. А для этого данные должны быть прочитаны. А если их еще не записывали? А потом вдруг пользователь понимает, что текущие значения хранимых переменных его более чем устраивают и надо их сохранить? А если надо восстановить те, которые устанавливал разработчик?

Вот и получается, что необходимы функции:

  1. Сохранение списка системных переменных с их значениями "по умолчанию" в .dat или .ini файл. При этом функция должна сразу создавать .dat / .ini файл
  2. Чтение и установка прочитанных значений системных переменных
  3. Сохранение текущих значений в dat / ini файл

Приступим?

Итак, сначала dat-файлы. Проще всего прямо в указанном файле хранить нечто типа

1
2
3
4
5
6
7
("elevation" . 0.0)
("blipmode" . 0)
("filedia" . 1)
("cmddia" . 1)
("attdia" . 1)
("attreq" . 1)
("insbase" 0.0 0.0 0.0)

Потом открывать файл и выполнять оттуда последовательное чтение данных.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(defun _lispru-sysvar-dat-set (default / handle str)
                           ;|
*    Установка системных переменных *lispru-sysvar-list* в их значения "по
* умолчанию". Установленные значения записываются в файл *lispru-sysvar-datfile*
*    Параметры вызова
  default   устанавливать значения "по умолчанию" (t) или читать из файла
            *lispru-sysvar-datfile* и устанавливать записанные
|;

  (if default
    (progn
      (setq handle (open *lispru-sysvar-datfile* "w"))
      (foreach item *lispru-sysvar-list*
        (vl-catch-all-apply
          (function
            (lambda ()
              (setvar (car item) (cdr item))
              ) ;_ end of lambda
            ) ;_ end of function
          ) ;_ end of vl-catch-all-apply
        (write-line (strcat "(""
                            (car item)
                            "
" "
                            (if (listp (cdr item))
                              ""
                              ". "
                              ) ;_ end of if
                            (vl-string-trim "()" (vl-princ-to-string (cdr item)))
                            ")"
                            ) ;_ end of strcat
                    handle
                    ) ;_ end of write-line
        ) ;_ end of foreach
      (close handle)
      ) ;_ end of progn
    (if (not (findfile *lispru-sysvar-datfile*))
      (_lispru-sysvar-set t)
      (progn
        (setq handle (open *lispru-sysvar-datfile* "r"))
        (while (setq str (read-line handle))
          (vl-catch-all-apply
            (function
              (lambda ()
                (setq str (read str))
                (setvar (car str) (cdr str))
                ) ;_ end of lambda
              ) ;_ end of function
            ) ;_ end of vl-catch-all-apply
          ) ;_ end of while
        (close handle)
        ) ;_ end of progn
      ) ;_ end of if
    ) ;_ end of if
  ) ;_ end of defun

Код здесь.

Если убрать постоянные проверки на ошибки (вызовы vl-catch-*), то все станет достаточно просто. Оставлю эту задачку, неохота ее разбирать ;)

По идее можно сделать дополнительно информационное окошко о том, какие значения не удалось установить, но я предполагаю, что эти файлы все же не будут правиться вручную :)

Можно разработать другой вариант той же функции, работающий по другому алгоритму, без рекурсии: сначала анализируется параметр default, и, если он t, то выполняется запись в файл *lispru-sysvar-datfile*, потом файл открывается на чтение и уже независимо ни от чего оттуда считываются пары переменная - значение и устанавливаются системные переменные.

Осталось написать функцию чтения имеющихся значений и записи их в *lispru-sysvar-datfile*:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(defun _lispru-sysvar-dat-save (/ handle)
                           ;|
*    Чтение установленных в текущем файле значений системных переменных
* из *lispru-sysvar-list* и запись их в *lispru-sysvar-datfile*
|;

  (setq handle (open *lispru-sysvar-datfile* "w"))
  (foreach item *lispru-sysvar-list*
    (write-line (strcat "(""
                        (car item)
                        "
" "
                        (if (listp (cdr item))
                          ""
                          ". "
                          ) ;_ end of if
                        (vl-string-trim "()" (vl-princ-to-string (cdr item)))
                        ")"
                        ) ;_ end of strcat
                handle
                ) ;_ end of write-line
    ) ;_ end of foreach
  (close handle)
  ) ;_ end of defun

Код здесь.

Теперь, комбинируя эти функции, можно написать лисп, который при старте нового файла сразу будет устанавливать эти системные переменные в значения "по умолчанию".

Небольшое резюме... Используя такой метод - прямого сохранения списков в файл - можно решать весьма серьезные задачи. Но какова информативность таких файлов? Если что-то "упало", и для восстановления потребуется такие файлы править вручную, то это будет ад. Такие файлы никак не задокументируешь, изменение алгоритма работы превращается в пытку. На каждый "чих" придется делать отдельный файл, не забывая его "закрывать".

В общем, лично я не в восторге от такого бардака. Изыскивая пути "ухода", я сначала нашел ini-файлы, потом реестр, и, в последнюю очередь - xml-файлы.

Ффуууууххх, еле дописал. Теперь точно надолго замолчу :)

---
Исходные коды, упомянутые на странице:

Имя файла Выполняемые действия
lispru-sysvar-list.lsp Создание *lispru-susvar-list*
_lispru-dir-create.lsp Функция практически гарантированного создания каталога
lispru-sysvar-datfile.lsp Создание *lispru-sysvar-datfile*
lispru-sysvar-inifile.lsp Создание *lispru-sysvar-inifile*
_lispru-sysvar-dat-set.lsp Функция установки системных переменных
_lispru-sysvar-dat-save.lsp Функция сохранения текущих значений системных переменных

Размещено в Код LISP, Функции LISP · Метки: ,



Комментарии

Есть 1 комментарий к “Хранение пользовательских типов данных. Часть 3.1. Хранение в файлах.”

Трэкбэки

Узнайте, что другие говорят про эту заметку...
  1. [...] разговор, начатый здесь и продолженный в части 3.1. Хранение в файлах списков, которые потом читаются и [...]



Поделитесь своим мнением


Я не робот.