Skip to content

Commit 1cd0ed1

Browse files
authored
Merge pull request #293 from AronMav/fix/format-838-readwrite
Исправление повреждения базы при записи в формате 8.3.8, команды -ct/-dr и тесты
2 parents 9c657e4 + 0c511ab commit 1cd0ed1

20 files changed

Lines changed: 596 additions & 27 deletions

README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77
В апреле 2017 года автор проекта [Валерий Агеев](https://bitbucket.org/awa15/) поделился с сообществом [исходными кодами проекта](https://bitbucket.org/awa15/tool_1cd-sources/src). Изначально проект был написан на Borland C++ Builder, здесь мы занимаемся переносом на "ванильный C++" с поддержкой компиляторов gcc, mingw и msvc и постепенным переходом на более свежие стандарты языка C++.
88

9+
## Поддерживаемые версии
10+
11+
Программа работает с файлами `1CD` по версии формата, записанной в заголовке базы:
12+
13+
| Версия формата | Режим |
14+
|---|---|
15+
| 8.0.3, 8.0.5 | только чтение |
16+
| 8.1.0, 8.2.0, 8.2.14, 8.3.8 | чтение и запись |
17+
18+
Формат `8.3.8` используют все платформы 1С:Предприятие 8.3.8 и новее (в том числе 8.3.x и 8.5.x) — их базы открываются как `8.3.8`. Версия формата в заголовке не обязательно совпадает с версией платформы, создавшей базу. Базы с другой версией формата не поддерживаются.
19+
920
## Установка
1021

1122
Проект в процессе разработки.
@@ -98,5 +109,17 @@ sudo apt-get install ctool1cd
98109

99110
Найти потерянные объекты и сохранить.
100111

112+
`-sts, -SaveTablesSize <путь>`
113+
114+
Сохранить размеры таблиц базы в указанный файл.
115+
116+
`-ct, -ClearTable <таблица>`
117+
118+
Пометить удалёнными все записи указанной таблицы. Команда изменяет базу.
119+
120+
`-dr, -DeleteRecord <таблица> <номер>`
121+
122+
Пометить удалённой одну запись таблицы по её физическому номеру. Команда изменяет базу.
123+
101124
Если в пути содержатся пробелы, его необходимо заключать в кавычки. Пути следует указывать без завершающего слеша `/` и бэкслеша `\`
102125
Для команд `-dc`, `-ddc`, `-drc` вместо пути можно указывать имя файла конфигурации (имя файла должно заканчиваться на `.cf`).

src/ctool1cd/App.cpp

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
#include "ParseCommandLine.h"
3131
#include "ErrorCode.h"
3232
#include "Messenger.h"
33+
#include "Table.h"
34+
#include "TableRecord.h"
3335

3436
extern Registrator msreg_g;
3537

@@ -288,6 +290,56 @@ void App::import_from_binary(const ParsedCommand &pc)
288290

289291
} // import_from_binary
290292

293+
void App::clear_table(const ParsedCommand &pc)
294+
{
295+
const string tname = LowerCase(pc.param1);
296+
for (int j = 0; j < base1CD->get_numtables(); j++) {
297+
Table *tbl = base1CD->get_table(j);
298+
if (LowerCase(tbl->get_name()) != tname) {
299+
continue;
300+
}
301+
tbl->begin_edit();
302+
uint32_t total = tbl->get_phys_numrecords();
303+
uint32_t deleted = 0;
304+
for (uint32_t i = 1; i < total; i++) {
305+
TableRecord *rec = tbl->get_record(i);
306+
bool removed = rec->is_removed();
307+
delete rec;
308+
if (!removed) {
309+
tbl->mark_record_removed(i);
310+
deleted++;
311+
}
312+
}
313+
base1CD->flush();
314+
msreg_g.AddMessage("Очистка таблицы выполнена.", MessageState::Succesfull)
315+
.with("Таблица", tbl->get_name())
316+
.with("Удалено записей", (int)deleted)
317+
.with("Физических записей всего", (int)total);
318+
return;
319+
}
320+
msreg_g.AddError("Таблица для очистки не найдена.").with("Таблица", pc.param1);
321+
}
322+
323+
void App::delete_one_record(const ParsedCommand &pc)
324+
{
325+
const string tname = LowerCase(pc.param1);
326+
uint32_t idx = (uint32_t)strtoul(pc.param2.c_str(), nullptr, 10);
327+
for (int j = 0; j < base1CD->get_numtables(); j++) {
328+
Table *tbl = base1CD->get_table(j);
329+
if (LowerCase(tbl->get_name()) != tname) {
330+
continue;
331+
}
332+
tbl->begin_edit();
333+
tbl->mark_record_removed(idx);
334+
base1CD->flush();
335+
msreg_g.AddMessage("Запись помечена удалённой.", MessageState::Succesfull)
336+
.with("Таблица", tbl->get_name())
337+
.with("Физический номер записи", (int)idx);
338+
return;
339+
}
340+
msreg_g.AddError("Таблица не найдена.").with("Таблица", pc.param1);
341+
}
342+
291343
void App::save_config(const boost::filesystem::path& param_path)
292344
{
293345
if(!is_infobase()) {
@@ -660,6 +712,14 @@ int App::Run()
660712
save_tables_sizes(pc);
661713
break;
662714
}
715+
case Command::clear_table: {
716+
clear_table(pc);
717+
break;
718+
}
719+
case Command::delete_one_record: {
720+
delete_one_record(pc);
721+
break;
722+
}
663723
}
664724
}
665725
catch (string &s) {

src/ctool1cd/App.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class App {
7373

7474
void save_tables_sizes(const ParsedCommand& pc);
7575

76+
void clear_table(const ParsedCommand& pc);
77+
78+
void delete_one_record(const ParsedCommand& pc);
79+
7680
inline bool is_infobase() const;
7781

7882
};

src/ctool1cd/ParseCommandLine.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ CommandDefinition CommandParse::definitions[] =
7474
{"savelostobjects", Command::find_and_save_lost_objects, 1, ""}, // 37
7575
{"sts", Command::save_tables_size, 1, ""}, // 38
7676
{"savetablessize", Command::save_tables_size, 1, ""}, // 39
77+
{"ct", Command::clear_table, 1, ""}, // 40
78+
{"cleartable", Command::clear_table, 1, ""}, // 41
79+
{"dr", Command::delete_one_record, 2, ""}, // 42
80+
{"deleterecord", Command::delete_one_record, 2, ""}, // 43
7781
};
7882

7983

src/ctool1cd/ParseCommandLine.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ enum class Command
4747
import_from_binary, // загрузить таблицы из двоичных файлов, выгруженных экспортом
4848
find_and_save_lost_objects, // найти и сохранить потерянные объекты
4949
save_tables_size, // сохранить в файл размеры таблиц
50+
clear_table, // удалить все записи указанной таблицы
51+
delete_one_record, // пометить удалённой одну запись таблицы по физическому индексу
5052
};
5153

5254
struct CommandDefinition

src/gtool1cd/mainwindow.cpp

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#include "configurations_window.h"
3232
#include "about_dialog.h"
3333
#include <QSortFilterProxyModel>
34+
#include <QMenu>
35+
#include <QMessageBox>
3436

3537
void MainWindow::AddDetailedMessage(
3638
const std::string &description,
@@ -70,7 +72,13 @@ void MainWindow::show_table_context_menu(const QPoint &pos)
7072
QAction action_import_blob(tr("Импорт BLOB"), this);
7173
connect(&action_import_blob, SIGNAL(triggered()), this, SLOT(import_blob_file()));
7274
contextMenu.addAction(&action_import_blob);
73-
75+
76+
contextMenu.addSeparator();
77+
78+
QAction action_clear_table(tr("Очистить таблицу"), this);
79+
connect(&action_clear_table, SIGNAL(triggered()), this, SLOT(clear_table_action()));
80+
contextMenu.addAction(&action_clear_table);
81+
7482
contextMenu.exec(mapToGlobal(pos));
7583
}
7684

@@ -109,6 +117,58 @@ void MainWindow::import_blob_file()
109117
}
110118
}
111119

120+
void MainWindow::clear_table_action()
121+
{
122+
auto indexes = ui->tableListView->selectionModel()->selectedIndexes();
123+
if (indexes.empty()) {
124+
return;
125+
}
126+
127+
auto *proxy = qobject_cast<QSortFilterProxyModel*>(ui->tableListView->model());
128+
QModelIndex src = proxy ? proxy->mapToSource(indexes.first()) : indexes.first();
129+
Table *t = db->get_table(src.row());
130+
131+
if (db->get_readonly()) {
132+
QMessageBox::warning(this, tr("Очистка таблицы"),
133+
tr("База открыта только для чтения — очистка невозможна."));
134+
return;
135+
}
136+
137+
if (QMessageBox::warning(this, tr("Очистка таблицы"),
138+
tr("Пометить удалёнными ВСЕ записи таблицы «%1»?\nДействие изменяет базу и необратимо.")
139+
.arg(QString::fromStdString(t->get_name())),
140+
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
141+
return;
142+
}
143+
144+
uint32_t deleted = 0;
145+
try {
146+
t->begin_edit();
147+
uint32_t total = t->get_phys_numrecords();
148+
for (uint32_t i = 1; i < total; i++) {
149+
auto *rec = t->get_record(i);
150+
bool removed = rec->is_removed();
151+
delete rec;
152+
if (!removed) {
153+
t->mark_record_removed(i);
154+
deleted++;
155+
}
156+
}
157+
db->flush();
158+
} catch (DetailedException &ex) {
159+
QMessageBox::critical(this, tr("Очистка таблицы"), QString(ex.what()));
160+
return;
161+
}
162+
163+
auto it = table_windows.find(t);
164+
if (it != table_windows.end()) {
165+
static_cast<TableDataWindow*>(it.value())->reload();
166+
}
167+
168+
QMessageBox::information(this, tr("Очистка таблицы"),
169+
tr("Готово. Помечено удалёнными записей: %1.").arg(deleted));
170+
}
171+
112172
MainWindow::~MainWindow()
113173
{
114174
delete ui;

src/gtool1cd/mainwindow.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ private slots:
6767
void show_table_context_menu(const QPoint &);
6868

6969
void export_blob_file();
70-
70+
7171
void import_blob_file();
7272

73+
void clear_table_action();
74+
7375
private:
7476
Ui::MainWindow *ui;
7577
T_1CD *db;

src/gtool1cd/models/table_data_model.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,19 @@ const TableRecord *TableDataModel::getRecord(const QModelIndex &index) const
226226

227227
}
228228

229+
uint32_t TableDataModel::physicalRecordNo(const QModelIndex &index) const
230+
{
231+
return _index == nullptr
232+
? index.row()
233+
: _index->get_numrec(index.row());
234+
}
235+
236+
void TableDataModel::notifyRowChanged(int row)
237+
{
238+
emit dataChanged(index(row, 0, QModelIndex()),
239+
index(row, columnCount(QModelIndex()) - 1, QModelIndex()));
240+
}
241+
229242
TStream *TableDataModel::getBlobStream(const QModelIndex &index) const
230243
{
231244
Field *f = table->get_field(index.column());

src/gtool1cd/models/table_data_model.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ class TableDataModel : public QAbstractItemModel
5353

5454
const TableRecord *getRecord(const QModelIndex &index) const;
5555

56+
// Физический номер записи в таблице для строки модели
57+
uint32_t physicalRecordNo(const QModelIndex &index) const;
58+
59+
// Сообщить представлению, что строка изменилась (например, помечена удалённой)
60+
void notifyRowChanged(int row);
61+
5662
TStream *getBlobStream(const QModelIndex &index) const;
5763

5864
private:

src/gtool1cd/table_data_window.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include <QItemSelection>
3030
#include "models/skobka_tree_model.h"
3131
#include <QFileDialog>
32+
#include <QMenu>
33+
#include <QMessageBox>
3234
#include "models/v8catalog_tree_model.h"
3335

3436
QString index_presentation(Index *index)
@@ -81,6 +83,10 @@ TableDataWindow::TableDataWindow(QWidget *parent, Table *table)
8183
emit ui->indexChooseBox->activated(1);
8284
}
8385

86+
ui->dataView->setContextMenuPolicy(Qt::CustomContextMenu);
87+
connect(ui->dataView, SIGNAL(customContextMenuRequested(QPoint)),
88+
this, SLOT(show_data_context_menu(QPoint)));
89+
8490
ui->dataView->setFocus();
8591
}
8692

@@ -199,3 +205,54 @@ void TableDataWindow::on_saveBlobButton_clicked()
199205
auto model = static_cast<TableDataModel*>(ui->dataView->model());
200206
model->dumpBlob(index, filename);
201207
}
208+
209+
void TableDataWindow::reload()
210+
{
211+
on_indexChooseBox_activated(ui->indexChooseBox->currentIndex());
212+
}
213+
214+
void TableDataWindow::show_data_context_menu(const QPoint &pos)
215+
{
216+
if (!ui->dataView->currentIndex().isValid()) {
217+
return;
218+
}
219+
QMenu menu(this);
220+
QAction action_delete(tr("Удалить запись"), this);
221+
connect(&action_delete, SIGNAL(triggered()), this, SLOT(delete_record_action()));
222+
menu.addAction(&action_delete);
223+
menu.exec(ui->dataView->mapToGlobal(pos));
224+
}
225+
226+
void TableDataWindow::delete_record_action()
227+
{
228+
auto index = ui->dataView->currentIndex();
229+
if (!index.isValid()) {
230+
return;
231+
}
232+
auto *model = static_cast<TableDataModel*>(ui->dataView->model());
233+
uint32_t phys_numrecord = model->physicalRecordNo(index);
234+
235+
if (table->get_base()->get_readonly()) {
236+
QMessageBox::warning(this, tr("Удаление записи"),
237+
tr("База открыта только для чтения — удаление невозможно."));
238+
return;
239+
}
240+
241+
if (QMessageBox::warning(this, tr("Удаление записи"),
242+
tr("Пометить запись №%1 таблицы «%2» удалённой?\nДействие изменяет базу.")
243+
.arg(phys_numrecord).arg(QString::fromStdString(table->get_name())),
244+
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) {
245+
return;
246+
}
247+
248+
try {
249+
table->begin_edit();
250+
table->mark_record_removed(phys_numrecord);
251+
table->get_base()->flush();
252+
} catch (DetailedException &ex) {
253+
QMessageBox::critical(this, tr("Удаление записи"), QString(ex.what()));
254+
return;
255+
}
256+
257+
model->notifyRowChanged(index.row());
258+
}

0 commit comments

Comments
 (0)