Show sync errors in a dialog (#855)
* Show sync errors in a dialog. * Fixed a compile warning. * Improve windows bintray description. * Improve sync errors dialog ui. * Commented out debug code. * Add dialog header to sync errors dialog. TODO: There are lots of common logic used in FileBrowserDialog and SyncErrorsDialog. We should refactor out a base dialog class. * Load last 50 errors by default. * Fixed the styles of sync errors dialog. * Updated sync error messages. * Double click to open the problematic repo. And improved dialog header style. * Ignore close event since we always use one instance of the dialog. * Only update the model when errors are changed.
This commit is contained in:
parent
8b51ee600d
commit
eb8f79ce96
15 changed files with 813 additions and 5 deletions
|
@ -230,6 +230,7 @@ SET(moc_headers
|
|||
src/ui/event-details-dialog.h
|
||||
src/ui/event-details-tree.h
|
||||
src/ui/set-repo-password-dialog.h
|
||||
src/ui/sync-errors-dialog.h
|
||||
src/filebrowser/file-browser-manager.h
|
||||
src/filebrowser/file-browser-dialog.h
|
||||
src/filebrowser/file-browser-requests.h
|
||||
|
@ -368,6 +369,7 @@ SET(seafile_client_sources
|
|||
src/rpc/rpc-client.cpp
|
||||
src/rpc/local-repo.cpp
|
||||
src/rpc/clone-task.cpp
|
||||
src/rpc/sync-error.cpp
|
||||
src/ui/main-window.cpp
|
||||
src/ui/init-seafile-dialog.cpp
|
||||
src/ui/login-dialog.cpp
|
||||
|
@ -410,6 +412,7 @@ SET(seafile_client_sources
|
|||
src/ui/event-details-dialog.cpp
|
||||
src/ui/event-details-tree.cpp
|
||||
src/ui/set-repo-password-dialog.cpp
|
||||
src/ui/sync-errors-dialog.cpp
|
||||
src/filebrowser/file-browser-manager.cpp
|
||||
src/filebrowser/file-browser-dialog.cpp
|
||||
src/filebrowser/file-browser-requests.cpp
|
||||
|
|
|
@ -52,3 +52,8 @@ FileBrowserDialog QToolBar QToolButton#backwardButton {
|
|||
FileBrowserDialog QToolBar QToolButton#forwardButton {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
SyncErrorsDialog QWidget#mainWidget {
|
||||
border : 0;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
|
|
@ -13,3 +13,7 @@ RepoTreeView {
|
|||
FileTableView QHeaderView::section:first {
|
||||
padding-left: 36px;
|
||||
}
|
||||
|
||||
SyncErrorsTableView::item {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
|
71
qt.css
71
qt.css
|
@ -618,3 +618,74 @@ SharedItemsTableView QHeaderView::section {
|
|||
padding-top: 10px;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
SyncErrorsDialog {
|
||||
margin: 0;
|
||||
min-width: 700px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
SyncErrorsDialog QSizeGrip {
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
SyncErrorsDialog QWidget#mainWidget {
|
||||
border: 1px solid #333;
|
||||
border-radius: 5px;
|
||||
background-color: #f2f2f2;
|
||||
}
|
||||
|
||||
SyncErrorsDialog QWidget#mHeader {
|
||||
min-height: 25px;
|
||||
max-height: 35px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding-top: 2px;
|
||||
padding-bottom: 5px;
|
||||
/* border: 1px solid red; */
|
||||
}
|
||||
|
||||
SyncErrorsDialog QWidget#mHeader QLabel {
|
||||
padding-left: 60px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
SyncErrorsDialog QWidget#mHeader QPushButton {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
|
||||
SyncErrorsTableView {
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
background-color: white;
|
||||
qproperty-focusPolicy: NoFocus;
|
||||
}
|
||||
|
||||
SyncErrorsTableView QHeaderView::section {
|
||||
border: 0;
|
||||
padding: 8px;
|
||||
padding-left: 5px;
|
||||
outline: 0px;
|
||||
color: #b3b3b3;
|
||||
background-color: white;
|
||||
font-size: 12px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
SyncErrorsTableView::item {
|
||||
padding-top: 5px;
|
||||
padding-left: 0px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
SyncErrorsTableView::item :last {
|
||||
padding-right: 13px;
|
||||
}
|
||||
|
||||
SyncErrorsTableView::item:selected {
|
||||
background-color: #F9E0C7;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ BEGIN
|
|||
BLOCK "040904b0"
|
||||
BEGIN
|
||||
VALUE "CompanyName", "Seafile Ltd.\0"
|
||||
VALUE "FileDescription", "The seafile client executable\0"
|
||||
VALUE "FileDescription", "Seafile Client"
|
||||
VALUE "FileVersion", RC_VERSION_STR
|
||||
VALUE "InternalName", "seafile-applet\0"
|
||||
VALUE "OriginalFilename", "seafile-applet.exe\0"
|
||||
|
|
|
@ -20,6 +20,7 @@ extern "C" {
|
|||
#include "utils/utils.h"
|
||||
#include "local-repo.h"
|
||||
#include "clone-task.h"
|
||||
#include "sync-error.h"
|
||||
#include "api/commit-details.h"
|
||||
#include "rpc-client.h"
|
||||
|
||||
|
@ -1035,3 +1036,24 @@ bool SeafileRpcClient::getCommitDiff(const QString& repo_id,
|
|||
g_list_free (objlist);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SeafileRpcClient::getSyncErrors(std::vector<SyncError> *errors, int offset, int limit)
|
||||
{
|
||||
GError *error = NULL;
|
||||
GList *objlist = searpc_client_call__objlist(
|
||||
seafile_rpc_client_,
|
||||
"seafile_get_file_sync_errors",
|
||||
SEAFILE_TYPE_FILE_SYNC_ERROR,
|
||||
&error, 2, "int", offset, "int", limit);
|
||||
|
||||
|
||||
for (GList *ptr = objlist; ptr; ptr = ptr->next) {
|
||||
SyncError error = SyncError::fromGObject((GObject *)ptr->data);
|
||||
errors->push_back(error);
|
||||
}
|
||||
|
||||
g_list_foreach (objlist, (GFunc)g_object_unref, NULL);
|
||||
g_list_free (objlist);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ struct _CcnetClient;
|
|||
|
||||
class LocalRepo;
|
||||
class CloneTask;
|
||||
class SyncError;
|
||||
class CommitDetails;
|
||||
|
||||
class SeafileRpcClient : public QObject {
|
||||
|
@ -129,6 +130,8 @@ public:
|
|||
const QString& previous_commit_id,
|
||||
CommitDetails *details);
|
||||
|
||||
bool getSyncErrors(std::vector<SyncError> *errors, int offset=0, int limit=10);
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(SeafileRpcClient)
|
||||
|
||||
|
|
82
src/rpc/sync-error.cpp
Normal file
82
src/rpc/sync-error.cpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <glib-object.h>
|
||||
|
||||
#include "utils/utils.h"
|
||||
#include "sync-error.h"
|
||||
|
||||
SyncError SyncError::fromGObject(GObject *obj)
|
||||
{
|
||||
SyncError error;
|
||||
|
||||
char *repo_id = NULL;
|
||||
char *repo_name = NULL;
|
||||
char *path = NULL;
|
||||
int error_id = 0;
|
||||
qint64 timestamp = 0;
|
||||
|
||||
g_object_get (obj,
|
||||
"repo_id", &repo_id,
|
||||
"repo_name", &repo_name,
|
||||
"path", &path,
|
||||
"err_id", &error_id,
|
||||
"timestamp", ×tamp,
|
||||
NULL);
|
||||
|
||||
error.repo_id = repo_id;
|
||||
error.repo_name = QString::fromUtf8(repo_name);
|
||||
error.path = QString::fromUtf8(path);
|
||||
|
||||
error.error_id = error_id;
|
||||
error.timestamp = timestamp;
|
||||
|
||||
g_free (repo_id);
|
||||
g_free (repo_name);
|
||||
g_free (path);
|
||||
|
||||
error.translateErrorStr();
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
// Copied from seafile/daemon/repo-mgr.h
|
||||
#define SYNC_ERROR_ID_FILE_LOCKED_BY_APP 0
|
||||
#define SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP 1
|
||||
#define SYNC_ERROR_ID_FILE_LOCKED 2
|
||||
#define SYNC_ERROR_ID_INVALID_PATH 3
|
||||
#define SYNC_ERROR_ID_INDEX_ERROR 4
|
||||
#define SYNC_ERROR_ID_PATH_END_SPACE_PERIOD 5
|
||||
#define SYNC_ERROR_ID_PATH_INVALID_CHARACTER 6
|
||||
|
||||
void SyncError::translateErrorStr()
|
||||
{
|
||||
readable_time_stamp = translateCommitTime(timestamp);
|
||||
|
||||
switch (error_id) {
|
||||
case SYNC_ERROR_ID_FILE_LOCKED_BY_APP:
|
||||
error_str = QObject::tr("File is locked by another application");
|
||||
break;
|
||||
case SYNC_ERROR_ID_FOLDER_LOCKED_BY_APP:
|
||||
error_str = QObject::tr("Folder is locked by another application");
|
||||
break;
|
||||
case SYNC_ERROR_ID_FILE_LOCKED:
|
||||
error_str = QObject::tr("File is locked by another user");
|
||||
break;
|
||||
case SYNC_ERROR_ID_INVALID_PATH:
|
||||
error_str = QObject::tr("Path is invalid");
|
||||
break;
|
||||
case SYNC_ERROR_ID_INDEX_ERROR:
|
||||
error_str = QObject::tr("Error when indexing");
|
||||
break;
|
||||
case SYNC_ERROR_ID_PATH_END_SPACE_PERIOD:
|
||||
error_str = QObject::tr("Path ends with space or period character");
|
||||
break;
|
||||
case SYNC_ERROR_ID_PATH_INVALID_CHARACTER:
|
||||
error_str = QObject::tr("Path contains invalid characters like '|' or ':'");
|
||||
break;
|
||||
default:
|
||||
// unreachable
|
||||
qWarning("unknown sync error id %d", error_id);
|
||||
error_str = "";
|
||||
}
|
||||
}
|
38
src/rpc/sync-error.h
Normal file
38
src/rpc/sync-error.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#ifndef SEAFILE_CLIENT_RPC_SYNC_ERROR_H
|
||||
#define SEAFILE_CLIENT_RPC_SYNC_ERROR_H
|
||||
|
||||
#include <QString>
|
||||
#include <QMetaType>
|
||||
|
||||
struct _GObject;
|
||||
|
||||
class SyncError {
|
||||
public:
|
||||
QString repo_id;
|
||||
QString repo_name;
|
||||
QString path;
|
||||
qint64 timestamp;
|
||||
int error_id;
|
||||
|
||||
// Generated fields.
|
||||
QString readable_time_stamp;
|
||||
QString error_str;
|
||||
|
||||
static SyncError fromGObject(_GObject *obj);
|
||||
|
||||
void translateErrorStr();
|
||||
|
||||
bool operator==(const SyncError& rhs) const {
|
||||
return repo_id == rhs.repo_id
|
||||
&& repo_name == rhs.repo_name
|
||||
&& path == rhs.path
|
||||
&& error_id == rhs.error_id
|
||||
&& timestamp == rhs.timestamp;
|
||||
}
|
||||
|
||||
bool operator!=(const SyncError& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // SEAFILE_CLIENT_RPC_SYNC_ERROR_H
|
|
@ -437,8 +437,13 @@ void RepoTreeView::createActions()
|
|||
|
||||
connect(share_repo_to_group_action_, SIGNAL(triggered()), this, SLOT(shareRepoToGroup()));
|
||||
|
||||
// In German translation there is a "seafile" string, so need to use tr("..").arg(..) here
|
||||
open_in_filebrowser_action_ = new QAction(tr("&Open cloud file browser").arg(getBrand()), this);
|
||||
QString open_action_text = tr("&Open cloud file browser");
|
||||
if (open_action_text.contains("%")) {
|
||||
// In German translation there is a "seafile" string, so need to use tr("..").arg(..) here
|
||||
open_action_text = open_action_text.arg(getBrand());
|
||||
}
|
||||
|
||||
open_in_filebrowser_action_ = new QAction(open_action_text, this);
|
||||
open_in_filebrowser_action_->setIcon(QIcon(":/images/cloud-gray.png"));
|
||||
open_in_filebrowser_action_->setStatusTip(tr("open this library in embedded Cloud File Browser"));
|
||||
open_in_filebrowser_action_->setIconVisibleInMenu(true);
|
||||
|
|
437
src/ui/sync-errors-dialog.cpp
Normal file
437
src/ui/sync-errors-dialog.cpp
Normal file
|
@ -0,0 +1,437 @@
|
|||
#include <QtGlobal>
|
||||
|
||||
#include <QtWidgets>
|
||||
#include <QTableView>
|
||||
#include <QResizeEvent>
|
||||
#include <QTimer>
|
||||
#include <QDesktopServices>
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include "QtAwesome.h"
|
||||
#include "utils/utils.h"
|
||||
#include "seafile-applet.h"
|
||||
#include "rpc/rpc-client.h"
|
||||
#include "rpc/sync-error.h"
|
||||
#include "rpc/local-repo.h"
|
||||
#include "sync-errors-dialog.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const int kUpdateErrorsIntervalMSecs = 3000;
|
||||
|
||||
const int kDefaultColumnWidth = 120;
|
||||
const int kDefaultColumnHeight = 40;
|
||||
|
||||
const int kRepoNameColumnWidth = 100;
|
||||
const int kPathColumnWidth = 150;
|
||||
const int kErrorColumnWidth = 200;
|
||||
const int kTimestampColumnWidth = 80;
|
||||
const int kExtraPadding = 80;
|
||||
|
||||
const int kDefaultColumnSum = kRepoNameColumnWidth + kPathColumnWidth + kErrorColumnWidth + kTimestampColumnWidth + kExtraPadding;
|
||||
|
||||
enum {
|
||||
INDEX_EMPTY_VIEW = 0,
|
||||
INDEX_TABE_VIEW
|
||||
};
|
||||
|
||||
enum {
|
||||
COLUMN_REPO_NAME = 0,
|
||||
COLUMN_PATH,
|
||||
COLUMN_ERROR_STR,
|
||||
COLUMN_TIMESTAMP,
|
||||
MAX_COLUMN,
|
||||
};
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
// TODO: There are lots of common logic used in FileBrowserDialog and
|
||||
// SyncErrorsDialog. We should refactor out a base dialog class.
|
||||
SyncErrorsDialog::SyncErrorsDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
// setupUi(this);
|
||||
|
||||
setWindowTitle(tr("File Sync Errors"));
|
||||
setWindowIcon(QIcon(":/images/seafile.png"));
|
||||
setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint & ~Qt::Dialog)
|
||||
#if !defined(Q_OS_MAC)
|
||||
| Qt::FramelessWindowHint
|
||||
#endif
|
||||
| Qt::Window);
|
||||
|
||||
resizer_ = new QSizeGrip(this);
|
||||
resizer_->resize(resizer_->sizeHint());
|
||||
setAttribute(Qt::WA_TranslucentBackground, true);
|
||||
|
||||
createTitleBar();
|
||||
createEmptyView();
|
||||
|
||||
table_ = new SyncErrorsTableView;
|
||||
model_ = new SyncErrorsTableModel(this);
|
||||
table_->setModel(model_);
|
||||
|
||||
QWidget* widget = new QWidget;
|
||||
widget->setObjectName("mainWidget");
|
||||
QVBoxLayout* layout = new QVBoxLayout;
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
setLayout(layout);
|
||||
layout->addWidget(widget);
|
||||
|
||||
QVBoxLayout *vlayout = new QVBoxLayout;
|
||||
vlayout->setContentsMargins(1, 0, 1, 0);
|
||||
vlayout->setSpacing(0);
|
||||
widget->setLayout(vlayout);
|
||||
|
||||
stack_ = new QStackedWidget;
|
||||
stack_->insertWidget(INDEX_EMPTY_VIEW, empty_view_);
|
||||
stack_->insertWidget(INDEX_TABE_VIEW, table_);
|
||||
stack_->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
vlayout->addWidget(header_);
|
||||
vlayout->addWidget(stack_);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
header_->setVisible(false);
|
||||
#endif
|
||||
|
||||
onModelReset();
|
||||
connect(model_, SIGNAL(modelReset()), this, SLOT(onModelReset()));
|
||||
}
|
||||
|
||||
void SyncErrorsDialog::createTitleBar()
|
||||
{
|
||||
header_ = new QWidget;
|
||||
header_->setObjectName("mHeader");
|
||||
QHBoxLayout *layout = new QHBoxLayout;
|
||||
layout->setContentsMargins(1, 1, 1, 1);
|
||||
layout->setSpacing(0);
|
||||
header_->setLayout(layout);
|
||||
|
||||
QSpacerItem *spacer1 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
layout->addSpacerItem(spacer1);
|
||||
|
||||
brand_label_ = new QLabel(windowTitle());
|
||||
brand_label_->setObjectName("mBrand");
|
||||
layout->addWidget(brand_label_);
|
||||
|
||||
QSpacerItem *spacer2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
|
||||
layout->addSpacerItem(spacer2);
|
||||
|
||||
minimize_button_ = new QPushButton;
|
||||
minimize_button_->setObjectName("mMinimizeBtn");
|
||||
minimize_button_->setToolTip(tr("Minimize"));
|
||||
minimize_button_->setIcon(awesome->icon(icon_minus, QColor("#808081")));
|
||||
layout->addWidget(minimize_button_);
|
||||
connect(minimize_button_, SIGNAL(clicked()), this, SLOT(showMinimized()));
|
||||
|
||||
close_button_ = new QPushButton;
|
||||
close_button_->setObjectName("mCloseBtn");
|
||||
close_button_->setToolTip(tr("Close"));
|
||||
close_button_->setIcon(awesome->icon(icon_remove, QColor("#808081")));
|
||||
layout->addWidget(close_button_);
|
||||
connect(close_button_, SIGNAL(clicked()), this, SLOT(close()));
|
||||
|
||||
header_->installEventFilter(this);
|
||||
}
|
||||
|
||||
bool SyncErrorsDialog::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
if (obj == header_) {
|
||||
if (event->type() == QEvent::MouseButtonPress) {
|
||||
QMouseEvent *ev = (QMouseEvent *)event;
|
||||
QRect frame_rect = frameGeometry();
|
||||
old_pos_ = ev->globalPos() - frame_rect.topLeft();
|
||||
return true;
|
||||
} else if (event->type() == QEvent::MouseMove) {
|
||||
QMouseEvent *ev = (QMouseEvent *)event;
|
||||
move(ev->globalPos() - old_pos_);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QDialog::eventFilter(obj, event);
|
||||
}
|
||||
|
||||
void SyncErrorsDialog::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
event->ignore();
|
||||
this->hide();
|
||||
}
|
||||
|
||||
void SyncErrorsDialog::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
resizer_->move(rect().bottomRight() - resizer_->rect().bottomRight());
|
||||
resizer_->raise();
|
||||
}
|
||||
|
||||
void SyncErrorsDialog::updateErrors()
|
||||
{
|
||||
model_->updateErrors();
|
||||
}
|
||||
|
||||
void SyncErrorsDialog::onModelReset()
|
||||
{
|
||||
if (model_->rowCount() == 0) {
|
||||
stack_->setCurrentIndex(INDEX_EMPTY_VIEW);
|
||||
} else {
|
||||
stack_->setCurrentIndex(INDEX_TABE_VIEW);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void SyncErrorsDialog::createEmptyView()
|
||||
{
|
||||
empty_view_ = new QWidget(this);
|
||||
|
||||
QVBoxLayout *layout = new QVBoxLayout;
|
||||
empty_view_->setLayout(layout);
|
||||
|
||||
QLabel *label = new QLabel;
|
||||
label->setText(tr("No sync errors."));
|
||||
label->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(label);
|
||||
}
|
||||
|
||||
SyncErrorsTableView::SyncErrorsTableView(QWidget *parent)
|
||||
: QTableView(parent)
|
||||
{
|
||||
verticalHeader()->hide();
|
||||
verticalHeader()->setDefaultSectionSize(36);
|
||||
horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
horizontalHeader()->setStretchLastSection(true);
|
||||
horizontalHeader()->setCascadingSectionResizes(true);
|
||||
horizontalHeader()->setHighlightSections(false);
|
||||
horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
setGridStyle(Qt::NoPen);
|
||||
setShowGrid(false);
|
||||
setContentsMargins(0, 0, 0, 0);
|
||||
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
|
||||
|
||||
setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
setMouseTracking(true);
|
||||
|
||||
createContextMenu();
|
||||
|
||||
connect(this, SIGNAL(doubleClicked(const QModelIndex&)),
|
||||
this, SLOT(onItemDoubleClicked(const QModelIndex&)));
|
||||
}
|
||||
|
||||
void SyncErrorsTableView::contextMenuEvent(QContextMenuEvent *event)
|
||||
{
|
||||
QPoint pos = event->pos();
|
||||
int row = rowAt(pos.y());
|
||||
qDebug("row = %d\n", row);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncErrorsTableModel *model = (SyncErrorsTableModel *)this->model();
|
||||
|
||||
SyncError error = model->errorAt(row);
|
||||
|
||||
prepareContextMenu(error);
|
||||
pos = viewport()->mapToGlobal(pos);
|
||||
context_menu_->exec(pos);
|
||||
}
|
||||
|
||||
void SyncErrorsTableView::prepareContextMenu(const SyncError& error)
|
||||
{
|
||||
}
|
||||
|
||||
void SyncErrorsTableView::createContextMenu()
|
||||
{
|
||||
context_menu_ = new QMenu(this);
|
||||
}
|
||||
|
||||
void SyncErrorsTableView::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QTableView::resizeEvent(event);
|
||||
SyncErrorsTableModel *m = (SyncErrorsTableModel *)(model());
|
||||
m->onResize(event->size());
|
||||
}
|
||||
|
||||
void SyncErrorsTableView::onItemDoubleClicked(const QModelIndex& index)
|
||||
{
|
||||
SyncErrorsTableModel *model = (SyncErrorsTableModel *)this->model();
|
||||
SyncError error = model->errorAt(index.row());
|
||||
|
||||
// printf("error repo id is %s\n", error.repo_id.toUtf8().data());
|
||||
if (!error.repo_id.isEmpty()) {
|
||||
LocalRepo repo;
|
||||
seafApplet->rpcClient()->getLocalRepo(error.repo_id, &repo);
|
||||
if (repo.isValid()) {
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(repo.worktree));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncErrorsTableModel::SyncErrorsTableModel(QObject *parent)
|
||||
: QAbstractTableModel(parent),
|
||||
repo_name_column_width_(kRepoNameColumnWidth),
|
||||
path_column_width_(kPathColumnWidth),
|
||||
error_column_width_(kErrorColumnWidth)
|
||||
{
|
||||
update_timer_ = new QTimer(this);
|
||||
connect(update_timer_, SIGNAL(timeout()), this, SLOT(updateErrors()));
|
||||
update_timer_->start(kUpdateErrorsIntervalMSecs);
|
||||
|
||||
updateErrors();
|
||||
}
|
||||
|
||||
void SyncErrorsTableModel::updateErrors()
|
||||
{
|
||||
std::vector<SyncError> errors;
|
||||
int ret = seafApplet->rpcClient()->getSyncErrors(&errors, 0, 50);
|
||||
if (ret < 0) {
|
||||
qDebug("failed to get sync errors");
|
||||
return;
|
||||
}
|
||||
|
||||
// SyncError fake_error;
|
||||
// fake_error.repo_id = "xxx";
|
||||
// fake_error.repo_name = "NotSoGood";
|
||||
// fake_error.path = "/tmp/NotSoGood/BadFile";
|
||||
// fake_error.error_id = 5;
|
||||
// fake_error.timestamp = 1483056000;
|
||||
// fake_error.translateErrorStr();
|
||||
// errors.push_back(fake_error);
|
||||
|
||||
if (errors_ == errors) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (errors_.size() != errors.size()) {
|
||||
beginResetModel();
|
||||
errors_ = errors;
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0, n = errors.size(); i < n; i++) {
|
||||
if (errors_[i] == errors[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
errors_[i] = errors[i];
|
||||
QModelIndex start = QModelIndex().child(i, 0);
|
||||
QModelIndex stop = QModelIndex().child(i, MAX_COLUMN - 1);
|
||||
emit dataChanged(start, stop);
|
||||
}
|
||||
}
|
||||
|
||||
int SyncErrorsTableModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
return errors_.size();
|
||||
}
|
||||
|
||||
int SyncErrorsTableModel::columnCount(const QModelIndex& parent) const
|
||||
{
|
||||
return MAX_COLUMN;
|
||||
}
|
||||
|
||||
void SyncErrorsTableModel::onResize(const QSize &size)
|
||||
{
|
||||
int extra_width = size.width() - kDefaultColumnSum;
|
||||
int extra_width_per_column = extra_width / 3;
|
||||
|
||||
repo_name_column_width_ = kRepoNameColumnWidth + extra_width_per_column;
|
||||
path_column_width_ = kPathColumnWidth + extra_width_per_column;
|
||||
error_column_width_ = kErrorColumnWidth + extra_width_per_column;
|
||||
|
||||
// name_column_width_ should be always larger than kPathColumnWidth
|
||||
if (errors_.empty())
|
||||
return;
|
||||
|
||||
// printf ("path_column_width_ = %d\n", path_column_width_);
|
||||
emit dataChanged(
|
||||
index(0, COLUMN_ERROR_STR),
|
||||
index(errors_.size() - 1 , COLUMN_ERROR_STR));
|
||||
}
|
||||
|
||||
QVariant SyncErrorsTableModel::data(const QModelIndex & index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
int column = index.column();
|
||||
|
||||
if (role == Qt::TextAlignmentRole)
|
||||
return Qt::AlignLeft + Qt::AlignVCenter;
|
||||
|
||||
if (role == Qt::ToolTipRole)
|
||||
return tr("Double click to open the library");
|
||||
|
||||
if (role == Qt::SizeHintRole) {
|
||||
int h = kDefaultColumnHeight;
|
||||
int w = kDefaultColumnWidth;
|
||||
switch (column) {
|
||||
case COLUMN_REPO_NAME:
|
||||
w = repo_name_column_width_;
|
||||
break;
|
||||
case COLUMN_PATH:
|
||||
w = path_column_width_;
|
||||
break;
|
||||
case COLUMN_ERROR_STR:
|
||||
w = error_column_width_;
|
||||
break;
|
||||
case COLUMN_TIMESTAMP:
|
||||
w = kTimestampColumnWidth;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return QSize(w, h);
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const SyncError &error = errors_[index.row()];
|
||||
|
||||
if (column == COLUMN_REPO_NAME) {
|
||||
return error.repo_name;
|
||||
} else if (column == COLUMN_PATH) {
|
||||
return QDir::toNativeSeparators(error.path);
|
||||
} else if (column == COLUMN_ERROR_STR) {
|
||||
return error.error_str;
|
||||
} else if (column == COLUMN_TIMESTAMP) {
|
||||
return error.readable_time_stamp;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant SyncErrorsTableModel::headerData(int section,
|
||||
Qt::Orientation orientation,
|
||||
int role) const
|
||||
{
|
||||
if (orientation == Qt::Vertical) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::TextAlignmentRole)
|
||||
return Qt::AlignLeft + Qt::AlignVCenter;
|
||||
|
||||
if (role != Qt::DisplayRole)
|
||||
return QVariant();
|
||||
|
||||
if (section == COLUMN_REPO_NAME) {
|
||||
return tr("Library");
|
||||
} else if (section == COLUMN_PATH) {
|
||||
return tr("Path");
|
||||
} else if (section == COLUMN_ERROR_STR) {
|
||||
return tr("Error");
|
||||
} else if (section == COLUMN_TIMESTAMP) {
|
||||
return tr("Time");
|
||||
}
|
||||
|
||||
|
||||
return QVariant();
|
||||
}
|
109
src/ui/sync-errors-dialog.h
Normal file
109
src/ui/sync-errors-dialog.h
Normal file
|
@ -0,0 +1,109 @@
|
|||
#ifndef SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H
|
||||
#define SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QTableView>
|
||||
#include <QHeaderView>
|
||||
#include <QAbstractTableModel>
|
||||
#include <QDialog>
|
||||
|
||||
#include "rpc/sync-error.h"
|
||||
|
||||
class QTimer;
|
||||
class QStackedWidget;
|
||||
class QResizeEvent;
|
||||
class QSizeGrip;
|
||||
class QLabel;
|
||||
class QEvent;
|
||||
|
||||
class SyncError;
|
||||
class SyncErrorsTableView;
|
||||
class SyncErrorsTableModel;
|
||||
|
||||
class SyncErrorsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SyncErrorsDialog(QWidget *parent=0);
|
||||
void updateErrors();
|
||||
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
void closeEvent(QCloseEvent *event);
|
||||
|
||||
private slots:
|
||||
void onModelReset();
|
||||
|
||||
private:
|
||||
void createTitleBar();
|
||||
void createEmptyView();
|
||||
|
||||
bool eventFilter(QObject *obj, QEvent *event);
|
||||
|
||||
// title bar (windows and osx only)
|
||||
QWidget *header_;
|
||||
QLabel *brand_label_;
|
||||
QPushButton *minimize_button_;
|
||||
QPushButton *close_button_;
|
||||
QPoint old_pos_;
|
||||
|
||||
QSizeGrip *resizer_;
|
||||
|
||||
QStackedWidget *stack_;
|
||||
SyncErrorsTableView *table_;
|
||||
SyncErrorsTableModel *model_;
|
||||
QWidget *empty_view_;
|
||||
};
|
||||
|
||||
class SyncErrorsTableView : public QTableView
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SyncErrorsTableView(QWidget *parent=0);
|
||||
|
||||
void contextMenuEvent(QContextMenuEvent *event);
|
||||
void resizeEvent(QResizeEvent *event);
|
||||
|
||||
private slots:
|
||||
void onItemDoubleClicked(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
void createContextMenu();
|
||||
void prepareContextMenu(const SyncError& error);
|
||||
|
||||
private:
|
||||
QMenu *context_menu_;
|
||||
};
|
||||
|
||||
|
||||
class SyncErrorsTableModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SyncErrorsTableModel(QObject *parent=0);
|
||||
|
||||
int rowCount(const QModelIndex& parent=QModelIndex()) const;
|
||||
int columnCount(const QModelIndex& parent=QModelIndex()) const;
|
||||
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const;
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
|
||||
|
||||
SyncError errorAt(size_t i) const { return (i >= errors_.size()) ? SyncError() : errors_[i]; }
|
||||
|
||||
void onResize(const QSize& size);
|
||||
|
||||
public slots:
|
||||
void updateErrors();
|
||||
|
||||
private:
|
||||
|
||||
std::vector<SyncError> errors_;
|
||||
QTimer *update_timer_;
|
||||
int repo_name_column_width_;
|
||||
int path_column_width_;
|
||||
int error_column_width_;
|
||||
};
|
||||
|
||||
#endif // SEAFILE_CLIENT_SYNC_ERRORS_DIALOG_H
|
|
@ -31,6 +31,7 @@ extern "C" {
|
|||
#include "seahub-notifications-monitor.h"
|
||||
#include "server-status-service.h"
|
||||
#include "api/commit-details.h"
|
||||
#include "sync-errors-dialog.h"
|
||||
|
||||
#include "tray-icon.h"
|
||||
#if defined(Q_OS_MAC)
|
||||
|
@ -112,7 +113,8 @@ SeafileTrayIcon::SeafileTrayIcon(QObject *parent)
|
|||
nth_trayicon_(0),
|
||||
rotate_counter_(0),
|
||||
state_(STATE_DAEMON_UP),
|
||||
next_message_msec_(0)
|
||||
next_message_msec_(0),
|
||||
sync_errors_dialog_(nullptr)
|
||||
{
|
||||
setState(STATE_DAEMON_DOWN);
|
||||
rotate_timer_ = new QTimer(this);
|
||||
|
@ -185,6 +187,10 @@ void SeafileTrayIcon::createActions()
|
|||
open_log_directory_action_->setStatusTip(tr("open %1 log folder").arg(getBrand()));
|
||||
connect(open_log_directory_action_, SIGNAL(triggered()), this, SLOT(openLogDirectory()));
|
||||
|
||||
show_sync_errors_action_ = new QAction(tr("Show file sync errors"), this);
|
||||
show_sync_errors_action_->setStatusTip(tr("Show file sync errors"));
|
||||
connect(show_sync_errors_action_, SIGNAL(triggered()), this, SLOT(showSyncErrorsDialog()));
|
||||
|
||||
about_action_ = new QAction(tr("&About"), this);
|
||||
about_action_->setStatusTip(tr("Show the application's About box"));
|
||||
connect(about_action_, SIGNAL(triggered()), this, SLOT(about()));
|
||||
|
@ -206,6 +212,7 @@ void SeafileTrayIcon::createContextMenu()
|
|||
context_menu_->addAction(open_seafile_folder_action_);
|
||||
context_menu_->addAction(settings_action_);
|
||||
context_menu_->addAction(open_log_directory_action_);
|
||||
context_menu_->addAction(show_sync_errors_action_);
|
||||
// context_menu_->addMenu(help_menu_);
|
||||
context_menu_->addSeparator();
|
||||
context_menu_->addAction(about_action_);
|
||||
|
@ -245,6 +252,7 @@ void SeafileTrayIcon::createGlobalMenuBar()
|
|||
global_menu_->addAction(open_seafile_folder_action_);
|
||||
global_menu_->addAction(settings_action_);
|
||||
global_menu_->addAction(open_log_directory_action_);
|
||||
global_menu_->addAction(show_sync_errors_action_);
|
||||
global_menu_->addSeparator();
|
||||
global_menu_->addAction(enable_auto_sync_action_);
|
||||
global_menu_->addAction(disable_auto_sync_action_);
|
||||
|
@ -653,3 +661,17 @@ void SeafileTrayIcon::checkTrayIconMessageQueue()
|
|||
commit_id_ = msg.commit_id;
|
||||
next_message_msec_ = now + kMessageDisplayTimeMSecs;
|
||||
}
|
||||
|
||||
|
||||
void SeafileTrayIcon::showSyncErrorsDialog()
|
||||
{
|
||||
// CloneTasksDialog dialog(this);
|
||||
if (sync_errors_dialog_ == nullptr) {
|
||||
sync_errors_dialog_ = new SyncErrorsDialog;
|
||||
}
|
||||
|
||||
sync_errors_dialog_->updateErrors();
|
||||
sync_errors_dialog_->show();
|
||||
sync_errors_dialog_->raise();
|
||||
sync_errors_dialog_->activateWindow();
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@ class QMenuBar;
|
|||
class TrayNotificationManager;
|
||||
#endif
|
||||
|
||||
class SyncErrorsDialog;
|
||||
|
||||
class SeafileTrayIcon : public QSystemTrayIcon {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -68,6 +70,8 @@ private slots:
|
|||
// only used on windows
|
||||
void onMessageClicked();
|
||||
|
||||
void showSyncErrorsDialog();
|
||||
|
||||
private:
|
||||
Q_DISABLE_COPY(SeafileTrayIcon)
|
||||
|
||||
|
@ -92,6 +96,7 @@ private:
|
|||
QAction *settings_action_;
|
||||
QAction *open_seafile_folder_action_;
|
||||
QAction *open_log_directory_action_;
|
||||
QAction *show_sync_errors_action_;
|
||||
QAction *view_unread_seahub_notifications_action_;
|
||||
|
||||
QAction *about_action_;
|
||||
|
@ -128,6 +133,8 @@ private:
|
|||
// displayed at least several seconds.
|
||||
QQueue<TrayMessage> pending_messages_;
|
||||
qint64 next_message_msec_;
|
||||
|
||||
SyncErrorsDialog *sync_errors_dialog_;
|
||||
};
|
||||
|
||||
#endif // SEAFILE_CLIENT_TRAY_ICON_H
|
||||
|
|
|
@ -117,7 +117,7 @@ int count_process (const char *process_name_in)
|
|||
HANDLE hProcess;
|
||||
DWORD length;
|
||||
int count = 0;
|
||||
int i, j;
|
||||
DWORD i;
|
||||
|
||||
if (strstr(process_name_in, ".exe")) {
|
||||
snprintf (name, sizeof(name), "%s", process_name_in);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue