Comparar commits
No hay commits en común. "15e50c7bab0301a69c86a639b78c20b15d2db465" y "a3cfb7ade1aeef4a1f44d189b0bc2c696b788e6c" tienen historias totalmente diferentes.
15e50c7bab
...
a3cfb7ade1
@ -1,7 +1,6 @@
|
|||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
#include "commandtag.h"
|
#include "commandtag.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "tagmanager.h"
|
|
||||||
|
|
||||||
int CommandTag::handle(QStringList arguments)
|
int CommandTag::handle(QStringList arguments)
|
||||||
{
|
{
|
||||||
@ -53,8 +52,7 @@ int CommandTag::handle(QStringList arguments)
|
|||||||
paths[i] = absolutePath;
|
paths[i] = absolutePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
TagManager tagManager{*this->dbService};
|
bool result = this->dbService->addTag(tag, paths);
|
||||||
bool result = tagManager.addPathsToTag(tag, paths);
|
|
||||||
if(!result)
|
if(!result)
|
||||||
{
|
{
|
||||||
Logger::error() << "Failed to assign tags" << Qt::endl;
|
Logger::error() << "Failed to assign tags" << Qt::endl;
|
||||||
|
@ -16,9 +16,6 @@
|
|||||||
#include <QScreen>
|
#include <QScreen>
|
||||||
#include <QProgressDialog>
|
#include <QProgressDialog>
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QWidgetAction>
|
|
||||||
#include <QInputDialog>
|
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "clicklabel.h"
|
#include "clicklabel.h"
|
||||||
@ -45,9 +42,6 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
|||||||
|
|
||||||
indexer = new Indexer(*(this->dbService));
|
indexer = new Indexer(*(this->dbService));
|
||||||
indexer->setParent(this);
|
indexer->setParent(this);
|
||||||
|
|
||||||
tagManager = new TagManager(*(this->dbService));
|
|
||||||
|
|
||||||
connectSignals();
|
connectSignals();
|
||||||
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||||
ui->tabWidget->setCurrentIndex(0);
|
ui->tabWidget->setCurrentIndex(0);
|
||||||
@ -1014,65 +1008,6 @@ void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
|
|||||||
this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
|
this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
QMenu *tagMenu = menu.addMenu("Tag file with: ");
|
|
||||||
QVector<QString> allTags = this->dbService->getTags();
|
|
||||||
QHash<QString, bool> fileTags;
|
|
||||||
|
|
||||||
QString path = fileInfo.absoluteFilePath();
|
|
||||||
|
|
||||||
for(const QString &fileTag : this->dbService->getTagsForPath(path))
|
|
||||||
{
|
|
||||||
fileTags[fileTag] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const QString &tag : allTags)
|
|
||||||
{
|
|
||||||
QCheckBox *checkBox = new QCheckBox(tagMenu);
|
|
||||||
QWidgetAction *checkableAction = new QWidgetAction(tagMenu);
|
|
||||||
checkableAction->setDefaultWidget(checkBox);
|
|
||||||
checkBox->setText(tag);
|
|
||||||
checkBox->setChecked(fileTags.contains(tag));
|
|
||||||
tagMenu->addAction(checkableAction);
|
|
||||||
|
|
||||||
connect(checkBox, &QCheckBox::stateChanged, this,
|
|
||||||
[this, checkBox, path]
|
|
||||||
{
|
|
||||||
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
|
|
||||||
QString checkBoxText = checkBox->text();
|
|
||||||
if(checkBox->isChecked())
|
|
||||||
{
|
|
||||||
if(!this->tagManager->addTagsToPath(path, {checkBoxText}))
|
|
||||||
{
|
|
||||||
QMessageBox::critical(this, "Error while adding tag",
|
|
||||||
"An error occured while trying to add the tag");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if(!this->tagManager->removeTagsForPath(path, {checkBoxText}))
|
|
||||||
{
|
|
||||||
QMessageBox::critical(this, "Error while removing tag",
|
|
||||||
"An error occured while trying to remove the tag");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
tagMenu->addAction("Add new tags", this,
|
|
||||||
[this, path]
|
|
||||||
{
|
|
||||||
bool ok;
|
|
||||||
QString text =
|
|
||||||
QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"),
|
|
||||||
QLineEdit::Normal, "", &ok);
|
|
||||||
|
|
||||||
if(ok && !this->tagManager->addTagsToPath(path, text, ','))
|
|
||||||
{
|
|
||||||
QMessageBox::critical(this, "Error while trying to add tags",
|
|
||||||
"An error occured while trying to add tags");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::openDocument(QString path, int num)
|
void MainWindow::openDocument(QString path, int num)
|
||||||
@ -1127,7 +1062,6 @@ MainWindow::~MainWindow()
|
|||||||
delete this->dbService;
|
delete this->dbService;
|
||||||
delete this->dbFactory;
|
delete this->dbFactory;
|
||||||
delete this->indexer;
|
delete this->indexer;
|
||||||
delete this->tagManager;
|
|
||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
#include "../shared/indexsyncer.h"
|
#include "../shared/indexsyncer.h"
|
||||||
#include "previewcoordinator.h"
|
#include "previewcoordinator.h"
|
||||||
#include "indexer.h"
|
#include "indexer.h"
|
||||||
#include "tagmanager.h"
|
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
@ -40,9 +39,6 @@ class MainWindow : public QMainWindow
|
|||||||
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
||||||
LooqsQuery contentSearchQuery;
|
LooqsQuery contentSearchQuery;
|
||||||
QVector<QString> searchHistory;
|
QVector<QString> searchHistory;
|
||||||
|
|
||||||
TagManager *tagManager;
|
|
||||||
|
|
||||||
int currentSearchHistoryIndex = 0;
|
int currentSearchHistoryIndex = 0;
|
||||||
QString currentSavedSearchText;
|
QString currentSavedSearchText;
|
||||||
bool previewDirty = false;
|
bool previewDirty = false;
|
||||||
|
@ -285,10 +285,6 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
|
|||||||
{
|
{
|
||||||
tokenType = FILTER_CONTENT_PAGE;
|
tokenType = FILTER_CONTENT_PAGE;
|
||||||
}
|
}
|
||||||
else if(filtername == "t" || filtername == "tag")
|
|
||||||
{
|
|
||||||
tokenType = FILTER_TAG_ASSIGNED;
|
|
||||||
}
|
|
||||||
// TODO: given this is not really a "filter", this feels slightly misplaced here
|
// TODO: given this is not really a "filter", this feels slightly misplaced here
|
||||||
else if(filtername == "sort")
|
else if(filtername == "sort")
|
||||||
{
|
{
|
||||||
|
@ -60,7 +60,6 @@ SOURCES += sqlitesearch.cpp \
|
|||||||
processor.cpp \
|
processor.cpp \
|
||||||
sandboxedprocessor.cpp \
|
sandboxedprocessor.cpp \
|
||||||
sqlitedbservice.cpp \
|
sqlitedbservice.cpp \
|
||||||
tagmanager.cpp \
|
|
||||||
tagstripperprocessor.cpp \
|
tagstripperprocessor.cpp \
|
||||||
utils.cpp \
|
utils.cpp \
|
||||||
../submodules/exile.h/exile.c \
|
../submodules/exile.h/exile.c \
|
||||||
@ -94,7 +93,6 @@ HEADERS += sqlitesearch.h \
|
|||||||
savefileresult.h \
|
savefileresult.h \
|
||||||
searchresult.h \
|
searchresult.h \
|
||||||
sqlitedbservice.h \
|
sqlitedbservice.h \
|
||||||
tagmanager.h \
|
|
||||||
tagstripperprocessor.h \
|
tagstripperprocessor.h \
|
||||||
token.h \
|
token.h \
|
||||||
common.h \
|
common.h \
|
||||||
|
@ -104,91 +104,6 @@ unsigned int SqliteDbService::getFiles(QVector<FileData> &results, QString wildC
|
|||||||
return processedRows;
|
return processedRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVector<QString> SqliteDbService::getTags()
|
|
||||||
{
|
|
||||||
QVector<QString> result;
|
|
||||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
|
||||||
query.prepare("SELECT name FROM tag ORDER by name ASC");
|
|
||||||
query.setForwardOnly(true);
|
|
||||||
if(!query.exec())
|
|
||||||
{
|
|
||||||
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
|
|
||||||
}
|
|
||||||
while(query.next())
|
|
||||||
{
|
|
||||||
QString tagname = query.value(0).toString();
|
|
||||||
result.append(tagname);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVector<QString> SqliteDbService::getTagsForPath(QString path)
|
|
||||||
{
|
|
||||||
QVector<QString> result;
|
|
||||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
|
||||||
query.prepare("SELECT name FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
|
|
||||||
"= file.id WHERE file.path = ? ORDER BY name ASC");
|
|
||||||
query.addBindValue(path);
|
|
||||||
query.setForwardOnly(true);
|
|
||||||
if(!query.exec())
|
|
||||||
{
|
|
||||||
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
|
|
||||||
}
|
|
||||||
while(query.next())
|
|
||||||
{
|
|
||||||
QString tagname = query.value(0).toString();
|
|
||||||
result.append(tagname);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SqliteDbService::setTags(QString path, const QSet<QString> &tags)
|
|
||||||
{
|
|
||||||
QSqlDatabase db = dbFactory->forCurrentThread();
|
|
||||||
if(!db.transaction())
|
|
||||||
{
|
|
||||||
Logger::error() << "Failed to open transaction for " << path << " : " << db.lastError() << Qt::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSqlQuery deletionQuery = QSqlQuery(db);
|
|
||||||
deletionQuery.prepare("DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?)");
|
|
||||||
deletionQuery.addBindValue(path);
|
|
||||||
if(!deletionQuery.exec())
|
|
||||||
{
|
|
||||||
db.rollback();
|
|
||||||
Logger::error() << "Failed to delete existing tags " << deletionQuery.lastError() << Qt::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const QString &tag : tags)
|
|
||||||
{
|
|
||||||
QSqlQuery tagQuery = QSqlQuery(db);
|
|
||||||
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
|
|
||||||
tagQuery.addBindValue(tag.toLower());
|
|
||||||
if(!tagQuery.exec())
|
|
||||||
{
|
|
||||||
db.rollback();
|
|
||||||
Logger::error() << "Failed to insert tag " << tagQuery.lastError() << Qt::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QSqlQuery fileTagQuery(db);
|
|
||||||
fileTagQuery.prepare(
|
|
||||||
"INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
|
|
||||||
"FROM tag WHERE name = ?))");
|
|
||||||
fileTagQuery.bindValue(0, path);
|
|
||||||
fileTagQuery.bindValue(1, tag);
|
|
||||||
if(!fileTagQuery.exec())
|
|
||||||
{
|
|
||||||
db.rollback();
|
|
||||||
Logger::error() << "Failed to assign tag to file" << Qt::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.commit();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData)
|
bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData)
|
||||||
{
|
{
|
||||||
QString ftsInsertStatement;
|
QString ftsInsertStatement;
|
||||||
@ -333,8 +248,6 @@ bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
|
|||||||
QSqlQuery tagQuery(db);
|
QSqlQuery tagQuery(db);
|
||||||
QSqlQuery fileTagQuery(db);
|
QSqlQuery fileTagQuery(db);
|
||||||
|
|
||||||
tag = tag.toLower();
|
|
||||||
|
|
||||||
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
|
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
|
||||||
tagQuery.addBindValue(tag);
|
tagQuery.addBindValue(tag);
|
||||||
|
|
||||||
|
@ -23,19 +23,13 @@ class SqliteDbService
|
|||||||
public:
|
public:
|
||||||
SqliteDbService(DatabaseFactory &dbFactory);
|
SqliteDbService(DatabaseFactory &dbFactory);
|
||||||
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
|
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
|
||||||
|
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
|
||||||
bool deleteFile(QString path);
|
bool deleteFile(QString path);
|
||||||
bool fileExistsInDatabase(QString path);
|
bool fileExistsInDatabase(QString path);
|
||||||
bool fileExistsInDatabase(QString path, qint64 mtime);
|
bool fileExistsInDatabase(QString path, qint64 mtime);
|
||||||
bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype);
|
bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype);
|
||||||
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
|
|
||||||
|
|
||||||
bool addTag(QString tag, QString path);
|
bool addTag(QString tag, QString path);
|
||||||
bool addTag(QString tag, const QVector<QString> &paths);
|
bool addTag(QString tag, const QVector<QString> &paths);
|
||||||
QVector<QString> getTags();
|
|
||||||
QVector<QString> getTagsForPath(QString path);
|
|
||||||
bool setTags(QString path, const QSet<QString> &tags);
|
|
||||||
|
|
||||||
QVector<SearchResult> search(const LooqsQuery &query);
|
QVector<SearchResult> search(const LooqsQuery &query);
|
||||||
|
|
||||||
std::optional<QChar> queryFileType(QString absPath);
|
std::optional<QChar> queryFileType(QString absPath);
|
||||||
|
@ -143,11 +143,6 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
|
|||||||
{
|
{
|
||||||
return {" fts MATCH ? ", {escapeFtsArgument(value)}};
|
return {" fts MATCH ? ", {escapeFtsArgument(value)}};
|
||||||
}
|
}
|
||||||
if(token.type == FILTER_TAG_ASSIGNED)
|
|
||||||
{
|
|
||||||
return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ",
|
|
||||||
{value.toLower()}};
|
|
||||||
}
|
|
||||||
throw LooqsGeneralException("Unknown token passed (should not happen)");
|
throw LooqsGeneralException("Unknown token passed (should not happen)");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,18 +179,18 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
|||||||
}
|
}
|
||||||
QString whereSqlTrigram = whereSql;
|
QString whereSqlTrigram = whereSql;
|
||||||
whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
|
whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
|
||||||
prepSql = "SELECT DISTINCT path, page, mtime, size, filetype FROM ("
|
prepSql =
|
||||||
|
"SELECT DISTINCT path, page, mtime, size, filetype FROM ("
|
||||||
"SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
"SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||||
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||||
"content.fileid "
|
"content.fileid "
|
||||||
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
||||||
whereSql +
|
whereSql +
|
||||||
"UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
"UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||||
"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON "
|
"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||||
"file.id = "
|
|
||||||
"content.fileid " +
|
"content.fileid " +
|
||||||
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " +
|
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram +
|
||||||
whereSqlTrigram + " ) " + sortSql;
|
" ) " + sortSql;
|
||||||
++bindIterations;
|
++bindIterations;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
#include "tagmanager.h"
|
|
||||||
|
|
||||||
TagManager::TagManager(SqliteDbService &dbService)
|
|
||||||
{
|
|
||||||
this->dbService = &dbService;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TagManager::addTagsToPath(QString path, const QSet<QString> &tags)
|
|
||||||
{
|
|
||||||
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
|
|
||||||
for(const QString &tag : tags)
|
|
||||||
{
|
|
||||||
currentTags.append(tag.toLower());
|
|
||||||
}
|
|
||||||
|
|
||||||
QSet<QString> newTags{currentTags.begin(), currentTags.end()};
|
|
||||||
return this->dbService->setTags(path, newTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TagManager::removeTagsForPath(QString path, const QSet<QString> &tags)
|
|
||||||
{
|
|
||||||
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
|
|
||||||
for(const QString &tag : tags)
|
|
||||||
{
|
|
||||||
currentTags.removeAll(tag);
|
|
||||||
}
|
|
||||||
QSet<QString> newTags{currentTags.begin(), currentTags.end()};
|
|
||||||
return this->dbService->setTags(path, newTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TagManager::addTagsToPath(QString path, QString tagstring, QChar delim)
|
|
||||||
{
|
|
||||||
auto splitted = tagstring.split(delim);
|
|
||||||
|
|
||||||
return addTagsToPath(path, QSet<QString>{splitted.begin(), splitted.end()});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TagManager::addPathsToTag(QString tag, const QVector<QString> &paths)
|
|
||||||
{
|
|
||||||
return this->dbService->addTag(tag, paths);
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
#ifndef TAGMANAGER_H
|
|
||||||
#define TAGMANAGER_H
|
|
||||||
#include "sqlitedbservice.h"
|
|
||||||
|
|
||||||
class TagManager
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
SqliteDbService *dbService = nullptr;
|
|
||||||
bool ensurePathOkay(QString inpath);
|
|
||||||
|
|
||||||
public:
|
|
||||||
TagManager(SqliteDbService &dbService);
|
|
||||||
|
|
||||||
bool addTagsToPath(QString path, const QSet<QString> &tags);
|
|
||||||
bool addTagsToPath(QString path, QString tagstring, QChar delim);
|
|
||||||
|
|
||||||
bool addPathsToTag(QString tag, const QVector<QString> &paths);
|
|
||||||
bool removeTagsForPath(QString path, const QSet<QString> &tags);
|
|
||||||
|
|
||||||
bool deleteTag(QString tag);
|
|
||||||
|
|
||||||
QVector<QString> getTags(QString path);
|
|
||||||
QVector<QString> getPaths(QString tag);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // TAGMANAGER_H
|
|
@ -22,7 +22,6 @@ enum TokenType
|
|||||||
FILTER_CONTENT = 512,
|
FILTER_CONTENT = 512,
|
||||||
FILTER_CONTENT_CONTAINS,
|
FILTER_CONTENT_CONTAINS,
|
||||||
FILTER_CONTENT_PAGE,
|
FILTER_CONTENT_PAGE,
|
||||||
FILTER_TAG_ASSIGNED,
|
|
||||||
LIMIT = 1024
|
LIMIT = 1024
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Cargando…
Referencia en una nueva incidencia
Block a user