Skip to content

Commit

Permalink
Merge pull request #45 from ikolomiko/data-migration-assistant
Browse files Browse the repository at this point in the history
Data migration assistant
  • Loading branch information
ikolomiko authored Oct 14, 2024
2 parents d42e877 + ff0b357 commit 64940c0
Show file tree
Hide file tree
Showing 9 changed files with 299 additions and 1 deletion.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ set(PROJECT_SOURCES
src/BLL/Authenticator/authenticator.h src/BLL/Authenticator/authenticator.cpp
src/BLL/Authenticator/licensestatus.h src/BLL/Authenticator/licensestatus.cpp
src/BLL/DatabaseHelper/databasehelper.cpp src/BLL/DatabaseHelper/databasehelper.h
src/BLL/DataMigration/datamigration.cpp src/BLL/DataMigration/datamigration.h
src/BLL/HistoryProvider/historyprovider.h src/BLL/HistoryProvider/historyprovider.cpp
src/BLL/MultiImportHelper/multiimporthelper.h src/BLL/MultiImportHelper/multiimporthelper.cpp
src/BLL/StudentEditor/studenteditor.cpp src/BLL/StudentEditor/studenteditor.h
Expand Down
154 changes: 154 additions & 0 deletions src/BLL/DataMigration/datamigration.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#include "datamigration.h"
#include "app.h"
#include "deps/libxlsxwriter/third_party/minizip/unzip.h"
#include "deps/libxlsxwriter/third_party/minizip/zip.h"
#include <QDirIterator>
#include <iostream>
#include <vector>

namespace ikoOSKAR {
namespace BLL {

// Returns true if the import is successful, false if not.
bool DataMigration::importZip(QString zipPath)
{
QString destPath = ikoOSKAR::App::pathDocsRoot;

// Make a backup first
QFile(ikoOSKAR::App::pathDocsRoot + "/database.db").copy(ikoOSKAR::App::pathDocsRoot + "/eski-database.db");

auto bPath = zipPath.toLocal8Bit();
char* path = bPath.data();
unzFile uf = unzOpen64(path);
if (!uf)
return false;

do {
char filename[256];
unz_file_info64 file_info;
if (unzGetCurrentFileInfo64(uf, &file_info, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) {
unzClose(uf);
return false;
}

QString fullPath = destPath + "/" + filename;

// Create directories if needed
QDir(fullPath).mkdir(".");

if (unzOpenCurrentFile(uf) != UNZ_OK) {
unzClose(uf);
return false;
}

std::string stdPath = fullPath.toStdString();
const char* cPath = stdPath.c_str();
FILE* fp = fopen(cPath, "wb");
if (fp) {
char buffer[8192];
int bytesRead;
while ((bytesRead = unzReadCurrentFile(uf, buffer, sizeof(buffer))) > 0) {
fwrite(buffer, 1, bytesRead, fp);
}
fclose(fp);
}

unzCloseCurrentFile(uf);
} while (unzGoToNextFile(uf) == UNZ_OK);

unzClose(uf);
return true;
}

// Returns the path of exported zip file. Returns empty option on error
std::optional<QString> DataMigration::exportZip()
{
QString sourcePath = ikoOSKAR::App::pathDocsRoot;
std::vector<std::string> inputFiles;

QDir sourceDir(sourcePath);
if (!sourceDir.exists()) {
std::cerr << "source dir does not exist" << std::endl;
return {};
}

auto bPath = zipFileExportPath.toLocal8Bit();
char* path = bPath.data();
zipFile zf = zipOpen64(path, APPEND_STATUS_CREATE);
if (!zf) {
std::cout << "could not open zip file uwu" << std::endl;
return {};
}

QDirIterator it(sourcePath, QDirIterator::Subdirectories);
while (it.hasNext()) {
QString filePath = it.next();
if (it.fileInfo().isFile()) {
inputFiles.push_back(filePath.toStdString());
}
}

for (const auto& file : inputFiles) {
zip_fileinfo zi = {};
auto bRelativePath = sourceDir.relativeFilePath(QString::fromStdString(file)).toLocal8Bit();
char* relativePath = bRelativePath.data();
if (zipOpenNewFileInZip(zf, relativePath, &zi, nullptr, 0, nullptr, 0, "This application was made by ikolomiko. If you steal this may Allah curse you to eternal damnation.", Z_DEFLATED, Z_DEFAULT_COMPRESSION) != ZIP_OK) {
zipClose(zf, nullptr);
std::cerr << "no open file in zip" << std::endl;
return {};
}

FILE* fp = fopen(file.c_str(), "rb");
if (fp) {
char buffer[8192];
int bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
zipWriteInFileInZip(zf, buffer, bytesRead);
}
fclose(fp);
}

zipCloseFileInZip(zf);
}

zipClose(zf, nullptr);

return { zipFileExportPath };
}

bool DataMigration::isZipFileValid(QString zipPath)
{
auto bPath = zipPath.toLocal8Bit();
char* path = bPath.data();
unzFile uf = unzOpen64(path);
if (!uf)
return false;

do {
char filename[1024];
unz_file_info64 file_info;
if (unzGetCurrentFileInfo64(uf, &file_info, filename, sizeof(filename), nullptr, 0, nullptr, 0) != UNZ_OK) {
unzClose(uf);
return false;
}

if (std::string(filename) == "database.db") {
unzClose(uf);
return true;
}
} while (unzGoToNextFile(uf) == UNZ_OK);

unzClose(uf);
return false;
}

bool DataMigration::copyZipFileInto(QString newPath)
{
std::cout << zipFileExportPath.toStdString() << std::endl;
if (QFile::exists(newPath)) {
QFile::remove(newPath);
}
return QFile::copy(zipFileExportPath, newPath);
}
}
}
21 changes: 21 additions & 0 deletions src/BLL/DataMigration/datamigration.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <QString>
#include <optional>
#include <QDir>

namespace ikoOSKAR {
namespace BLL {
class DataMigration {
public:
bool importZip(QString zipPath);
std::optional<QString> exportZip();
bool isZipFileValid(QString zipPath);
bool copyZipFileInto(QString newPath);

private:
const QString zipFileExportPath = QDir::tempPath() + "/ikoOSKAR-veriler.zip";
};

} // namespace BLL
} // namespace ikoOSKAR
3 changes: 2 additions & 1 deletion src/DAL/Database/database.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "database.h"
#include "app.h"

#include <QFile>
#include <QStandardPaths>
Expand Down Expand Up @@ -194,7 +195,7 @@ namespace DAL {

QString Database::GetDatabasePath(bool withFileName)
{
QString dirName = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
QString dirName = ikoOSKAR::App::pathDocsRoot;
if (!withFileName)
return dirName;

Expand Down
109 changes: 109 additions & 0 deletions src/UI/DatabasePage/databasepage.cpp
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include "databasepage.h"
#include "BLL/DataMigration/datamigration.h"
#include "UI/MultiImportDialog/multiimportdialog.h"
#include "UI/StudentEditorDialog/studenteditordialog.h"
#include "qheaderview.h"
#include "ui_databasepage.h"

#include "UI/ErrorUi/errorui.h"
#include <QFileDialog>
#include <QLabel>
#include <QMenu>
#include <QTableWidget>
Expand Down Expand Up @@ -165,11 +167,118 @@ namespace UI {
removeClassAction->setIcon(icon);
connect(removeClassAction, &QAction::triggered, this, &DatabasePage::actionRemoveClass_clicked);

QAction* importAction = new QAction("Uygulama verilerini içe aktar");
importAction->setIcon(icon);
connect(importAction, &QAction::triggered, this, &DatabasePage::importUserData);

QAction* exportAction = new QAction("Uygulama verilerini dışarı aktar");
exportAction->setIcon(icon);
connect(exportAction, &QAction::triggered, this, &DatabasePage::exportUserData);

menuMore->addAction(eotyAction);
menuMore->addAction(removeClassAction);
menuMore->addAction(importAction);
menuMore->addAction(exportAction);
ui->btnMore->setMenu(menuMore);
}

void DatabasePage::importUserData()
{
BLL::DataMigration migration;

auto response = QMessageBox::information(this,
"İçeri Aktar",
"Bu özellik, farklı bilgisayarlar arasında geçiş yaparken ikoOSKAR verilerini"
" taşımayı kolaylaştırmak içindir. Eski bilgisayardan \"Uygulama verilerini dışarı aktar\""
" seçeneğini seçerek bir zip dosyası oluşturduysanız o dosyayı şimdi içeri aktarabilirsiniz."
" Tamam butonuna tıkladıktan sonra içeri aktarılacak zip dosyasını seçiniz.",
QMessageBox::Ok | QMessageBox::Cancel);

if (response != QMessageBox::Ok) {
return;
}

QFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setNameFilters({ "ikoOSKAR-veriler.zip" });
dialog.setViewMode(QFileDialog::Detail);

QString zipPath;
if (dialog.exec() == QDialog::Accepted) {
zipPath = dialog.selectedFiles().at(0);
} else {
QMessageBox::warning(this, "Hata", "Geçerli bir dosya seçilmedi");
return;
}

if (!migration.isZipFileValid(zipPath)) {
QMessageBox::warning(this, "Hata", "Bu dosya geçerli bir ikoOSKAR veri yedeği dosyası değil!");
return;
}

if (!migration.importZip(zipPath)) {
QMessageBox::warning(this, "Hata", "Veriler içe aktarılırken bir hata oluştu");
return;
}

QMessageBox::information(this, "Başarılı", "Veriler başarıyla içe aktarıldı. Değişikliklerin etki etmesi için uygulama yeniden başlatılacak.");
emit restartApp();
}

void DatabasePage::exportUserData()
{
BLL::DataMigration migration;

auto response = QMessageBox::information(this,
"Dışarı Aktar",
"Bu özellik, farklı bilgisayarlar arasında geçiş yaparken ikoOSKAR verilerini"
" taşımayı kolaylaştırmak içindir. Birazdan oluşturacağınız zip dosyasını yeni bilgisayara kopyaladıktan sonra,"
" yeni bilgisayarda \"Uygulama verilerini içeri aktar\" seçeneğini seçerek ikoOSKAR verilerini taşıyabilirsiniz."
" Devam etmek için Tamam butonuna tıklayınız.",
QMessageBox::Ok | QMessageBox::Cancel);

if (response != QMessageBox::Ok) {
return;
}

std::optional<QString> result = migration.exportZip();
if (!result) {
QMessageBox::warning(this, "Hata", "Veriler dışarı aktarılırken bir hata oluştu!");
return;
}



response = QMessageBox::information(this,
"Dışarı Aktar",
"ikoOSKAR uygulama verilerinin bulunduğu yedek dosyası başarıyla oluşturuldu!"
" Tamam butonuna tıkladıktan sonra dosyayı kaydetmek istediğiniz konumu seçiniz.");

if (response != QMessageBox::Ok) {
return;
}

QString filePath = QFileDialog::getSaveFileName(this,
"Dışarı Aktar",
"ikoOSKAR-veriler.zip",
"zip dosyası (*.zip)");

if (filePath.isEmpty()) {
return;
}

bool success = migration.copyZipFileInto(filePath);
if (!success) {
QMessageBox::warning(this, "Hata", "İmkansız bir hata oluştu, dosya kopyalanamadı");
return;
}

QMessageBox::information(this,
"Dışarı Aktar",
"ikoOSKAR verileri başarıyla zip dosyasına aktarıldı. Veri aktarımına devam etmek için"
" bu dosyayı yeni bilgisayara aktarın ve ikoOSKAR'ı açıp \"Uygulama verilerini içe aktar\" seçeneğini tıklayın.");
}

DatabasePage::ClassTable::ClassTable(QList<Student*>* students)
{
setEditTriggers(QAbstractItemView::NoEditTriggers);
Expand Down
5 changes: 5 additions & 0 deletions src/UI/DatabasePage/databasepage.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ namespace UI {
ikoOSKAR::BLL::DatabaseHelper* bll;
inline static DatabasePage* instance;
explicit DatabasePage(QWidget* parent = nullptr);
void importUserData();
void exportUserData();

signals:
void restartApp();
};
} // namespace UI
} // namespace ikoOSKAR
Expand Down
3 changes: 3 additions & 0 deletions src/UI/MainWindow/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ namespace UI {
{
QPushButton* buttons[4] = { ui->btnHome, ui->btnDatabase, ui->btnSchemes, ui->btnHelp };
Common::Page* pages[4] = { WelcomePage::getInstance(), DatabasePage::getInstance(), SchemesPage::getInstance(), AboutPage::getInstance() };
connect(DatabasePage::getInstance(), &DatabasePage::restartApp, [this]() {
emit restartApp();
});

for (int i = 0; i < 4; i++) {
const auto& btn = buttons[i];
Expand Down
3 changes: 3 additions & 0 deletions src/UI/MainWindow/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ namespace UI {
void initSubpages();
void changePage(Subpage index, QIcon icon);
void refreshDemoStatus();

signals:
void restartApp();
};

} // namespace UI
Expand Down
1 change: 1 addition & 0 deletions src/app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void App::handleAuthSuccess(const QString& message)
authenticator->disconnect();

auto mainWindow = new UI::MainWindow();
connect(mainWindow, &UI::MainWindow::restartApp, this, &App::handleRestart);
mainWindow->show();
splash->finish(mainWindow);
}
Expand Down

0 comments on commit 64940c0

Please sign in to comment.