From 9f6dc1f3c29671d61fe6932263941926f7d75d50 Mon Sep 17 00:00:00 2001 From: oupson Date: Fri, 4 Mar 2022 08:49:21 +0100 Subject: [PATCH] working on sftp --- CMakeLists.txt | 5 +- includes/eventloop.hpp | 18 ++++-- includes/panel.hpp | 66 ++++++++++++++++++++++ includes/ssh.hpp | 12 +++- includes/term_windows.hpp | 8 ++- includes/vte.hpp | 9 +-- src/eventloop.cpp | 23 +++++--- src/panel.cpp | 112 ++++++++++++++++++++++++++++++++++++++ src/ssh.cpp | 14 ++++- src/term_windows.cpp | 23 +++++--- src/vte.cpp | 20 +++---- 11 files changed, 269 insertions(+), 41 deletions(-) create mode 100644 includes/panel.hpp create mode 100644 src/panel.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e2396bf..98240ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ include_directories( includes ) -add_executable(FooTerm src/main.cpp src/term_windows.cpp src/vte.cpp src/ssh.cpp src/eventloop.cpp) +add_executable(FooTerm src/main.cpp src/term_windows.cpp src/vte.cpp src/ssh.cpp src/eventloop.cpp src/panel.cpp includes/panel.hpp) target_link_libraries(FooTerm ${GTKMM_LIBRARIES}) @@ -32,8 +32,7 @@ target_link_libraries(FooTerm target_link_libraries(FooTerm ${VTE_LIBRARIES}) add_definitions(${VTE_CFLAGS} ${VTE_CFLAGS_OTHER}) - add_subdirectory(third-party) find_package(fmt) -target_link_libraries(FooTerm fmt::fmt ssh2) \ No newline at end of file +target_link_libraries(FooTerm fmt::fmt ssh2 pthread util) \ No newline at end of file diff --git a/includes/eventloop.hpp b/includes/eventloop.hpp index c63ad08..827bc3a 100644 --- a/includes/eventloop.hpp +++ b/includes/eventloop.hpp @@ -9,7 +9,9 @@ #include #include #include +#include +class FooTermWindow; struct EventLoopEntry { enum { @@ -17,8 +19,14 @@ struct EventLoopEntry { CHANNEL } entryType; - LIBSSH2_CHANNEL *channel; - int out; + union { + LIBSSH2_CHANNEL *channel; + LIBSSH2_SFTP *sftp; + }; + + union { + int fdout; + }; }; class EventLoop { @@ -26,12 +34,14 @@ private: std::vector pfds; std::vector outs; bool isClosed{}; + FooTermWindow* window; + public: - EventLoop(); + explicit EventLoop(FooTermWindow* window); void registerFd(int fdin, EventLoopEntry entry); - void run(); + [[noreturn]] void run(); static void start(EventLoop *self) { self->run(); diff --git a/includes/panel.hpp b/includes/panel.hpp new file mode 100644 index 0000000..ea902a9 --- /dev/null +++ b/includes/panel.hpp @@ -0,0 +1,66 @@ +// +// Created by oupson on 01/03/2022. +// + +#ifndef FOOTERM_PANEL_HPP +#define FOOTERM_PANEL_HPP + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vte.hpp" + +class FileModel : public Gtk::TreeModel::ColumnRecord { +public: + FileModel() { + add(colName); + add(colSize); + } + + Gtk::TreeModelColumn colName; + Gtk::TreeModelColumn colSize; +}; + +class Panel { +private: + Gtk::Paned paned; + Vte vte; + Session session; + + FileModel fileModel; + std::filesystem::path path; + + Gtk::ScrolledWindow mScrolledWindow; + Gtk::TreeView mTreeView; + Glib::RefPtr mRefTreeModel; + + void onCellDoubleClicked(const Gtk::TreePath &treePath, [[maybe_unused]] Gtk::TreeViewColumn *column); + + void openSession(const char *host, int port, const char *username, const char *password); + +public: + Panel(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password); + + void listFiles(); + + Gtk::Paned &getPaned() { + return this->paned; + } + + Vte &getVte() { + return this->vte; + } + + Session &getSession() { + return this->session; + } +}; + +#endif //FOOTERM_PANEL_HPP diff --git a/includes/ssh.hpp b/includes/ssh.hpp index a794105..df6ccfb 100644 --- a/includes/ssh.hpp +++ b/includes/ssh.hpp @@ -6,6 +6,7 @@ #define FOOTERM_SSH_HPP #include +#include #include #include #include @@ -15,6 +16,7 @@ private: LIBSSH2_SESSION *session; LIBSSH2_CHANNEL *channel; int sock; + LIBSSH2_SFTP* sftpSession; static int openSocket(const char *addr, int port); @@ -33,13 +35,21 @@ public: bool isConnected(); - int getSock() { + [[nodiscard]] int getSock() const { return this->sock; }; + LIBSSH2_SESSION *getSession() { + return this->session; + } + LIBSSH2_CHANNEL *getChannel() { return this->channel; } + + LIBSSH2_SFTP* getSftp() { + return this->sftpSession; + } }; class SessionConnectException : public std::exception { diff --git a/includes/term_windows.hpp b/includes/term_windows.hpp index eb10587..abb2971 100644 --- a/includes/term_windows.hpp +++ b/includes/term_windows.hpp @@ -5,22 +5,28 @@ #ifndef FOOTERM_TERM_WINDOWS_HPP #define FOOTERM_TERM_WINDOWS_HPP - #include #include #include #include +#include "panel.hpp" #include "eventloop.hpp" class FooTermWindow : public Gtk::Window { private: Gtk::Notebook notebook; + std::vector panels; EventLoop eventLoop; + void on_button_click(); public: FooTermWindow(); + + std::vector &getPanels() { + return panels; + } }; #endif //FOOTERM_TERM_WINDOWS_HPP diff --git a/includes/vte.hpp b/includes/vte.hpp index 8b6e80c..6087914 100644 --- a/includes/vte.hpp +++ b/includes/vte.hpp @@ -9,17 +9,18 @@ #include #include "eventloop.hpp" +#include "ssh.hpp" class Vte { +private: + GtkWidget *terminal; + public: Vte(); - void spawnShell(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password); + void spawnShell(EventLoop &eventLoop, Session& session); Gtk::Widget *asGtkWidget(); - -private: - GtkWidget *terminal; }; #endif //FOOTERM_VTE_HPP diff --git a/src/eventloop.cpp b/src/eventloop.cpp index d093720..65a83b0 100644 --- a/src/eventloop.cpp +++ b/src/eventloop.cpp @@ -2,11 +2,15 @@ // Created by oupson on 01/03/2022. // -#include "eventloop.hpp" #include #include -EventLoop::EventLoop() = default; +#include "eventloop.hpp" +#include "term_windows.hpp" + +EventLoop::EventLoop(FooTermWindow *window) { + this->window = window; +} void EventLoop::registerFd(int fdin, EventLoopEntry entry) { pollfd fd = {0}; @@ -18,16 +22,21 @@ void EventLoop::registerFd(int fdin, EventLoopEntry entry) { #define BUFFER_SIZE (256) -void EventLoop::run() { +[[noreturn]] void EventLoop::run() { char buffer[BUFFER_SIZE]; ssize_t bytesRead; int res; -#pragma clang diagnostic push -#pragma ide diagnostic ignored "EndlessLoop" while (!this->isClosed) { res = poll(this->pfds.data(), this->pfds.size(), 1000); + if (res == 0) + for (auto &value: this->window->getPanels()) { + auto session = value.getSession().getSession(); + + libssh2_keepalive_send(session, nullptr); + } + if (res > 0) { int i = 0; @@ -40,7 +49,7 @@ void EventLoop::run() { bytesRead = libssh2_channel_read(this->outs[i].channel, buffer, BUFFER_SIZE); if (bytesRead > 0) { - write(this->outs[i].out, buffer, bytesRead); + write(this->outs[i].fdout, buffer, bytesRead); } else if (bytesRead < 0 && bytesRead != LIBSSH2_ERROR_EAGAIN) { std::cout << bytesRead << std::endl; } @@ -62,8 +71,6 @@ void EventLoop::run() { } i++; } - } } -#pragma clang diagnostic pop } \ No newline at end of file diff --git a/src/panel.cpp b/src/panel.cpp new file mode 100644 index 0000000..e53fef0 --- /dev/null +++ b/src/panel.cpp @@ -0,0 +1,112 @@ +// +// Created by oupson on 01/03/2022. +// + +#include +#include + +#include + +#include + +#include "panel.hpp" + +Panel::Panel(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password) : path("/") { + this->openSession(host, port, username, password); + this->vte.spawnShell(eventLoop, this->session); + + this->paned.pack2(*this->vte.asGtkWidget()); + this->paned.pack1(this->mScrolledWindow); + mScrolledWindow.add(mTreeView); + + mScrolledWindow.set_policy(Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC); + // mScrolledWindow.set_expand(); + + mScrolledWindow.set_size_request(50); + + mRefTreeModel = Gtk::ListStore::create(fileModel); + + auto sortModel = Gtk::TreeModelSort::create(mRefTreeModel); + mTreeView.set_model(sortModel); + + sortModel->set_sort_column(0, Gtk::SortType::SORT_ASCENDING); + + mTreeView.append_column("Name", fileModel.colName); + mTreeView.append_column("Size", fileModel.colSize); + + mTreeView.signal_row_activated().connect(sigc::mem_fun(this, &Panel::onCellDoubleClicked)); + + auto pColumn1 = mTreeView.get_column(0); + pColumn1->set_sort_column(fileModel.colName); + + auto pColumn2 = mTreeView.get_column(1); + pColumn2->set_sort_column(fileModel.colSize); + + listFiles(); + + this->paned.show_all(); +} + +void Panel::openSession(const char *host, int port, const char *username, const char *password) { + session.openConnection(host, port, username, password); +} + +void Panel::onCellDoubleClicked(const Gtk::TreePath &treePath, [[maybe_unused]] Gtk::TreeViewColumn *column) { + auto row = *this->mTreeView.get_selection()->get_selected(); + std::string nPath = row.get_value(fileModel.colName); + + this->path.append(nPath); + + listFiles(); +} + +void Panel::listFiles() { + LIBSSH2_SFTP_HANDLE *dir; + int rc; + + do { + dir = libssh2_sftp_opendir(session.getSftp(), this->path.c_str()); + } while (dir == nullptr && libssh2_session_last_errno(session.getSession()) == LIBSSH2_ERROR_EAGAIN); + + if (dir == nullptr && libssh2_session_last_errno(session.getSession()) < 0) { + char *error; + int errorLen; + libssh2_session_last_error(session.getSession(), &error, &errorLen, true); + + throw SessionConnectException(fmt::format("unable to opendir \"{}\": {}", this->path.c_str(), error)); + } + + char buffer[1024]; + LIBSSH2_SFTP_ATTRIBUTES attrs; + + this->mRefTreeModel->clear(); + + do { + rc = libssh2_sftp_readdir(dir, buffer, sizeof(buffer) - 1, &attrs); + + if (rc > 0 && !(buffer[0] == '.' && buffer[1] == 0)) { + auto row = *mRefTreeModel->append(); + row[this->fileModel.colName] = buffer; + row[this->fileModel.colSize] = attrs.filesize; + //std::cerr << buffer << std::endl; + } + } while (rc > 0 || rc == LIBSSH2_ERROR_EAGAIN); + + if (rc < 0) { + char *error; + int errorLen; + libssh2_session_last_error(session.getSession(), &error, &errorLen, true); + throw SessionConnectException(fmt::format("unable to readdir \"{}\": {}", this->path.c_str(), error)); + } + + do { + rc = libssh2_sftp_closedir(dir); + } while (rc == LIBSSH2_ERROR_EAGAIN); + + if (rc < 0) { + char *error; + int errorLen; + libssh2_session_last_error(session.getSession(), &error, &errorLen, true); + throw SessionConnectException(fmt::format("unable to closedir \"{}\": {}", this->path.c_str(), error)); + } +} \ No newline at end of file diff --git a/src/ssh.cpp b/src/ssh.cpp index 6974653..8ee9e3f 100644 --- a/src/ssh.cpp +++ b/src/ssh.cpp @@ -2,22 +2,26 @@ // Created by oupson on 01/03/2022. // -#include "ssh.hpp" - #include #include #include #include + #include +#include "ssh.hpp" + Session::Session() { this->session = nullptr; this->channel = nullptr; this->sock = -1; + this->sftpSession = nullptr; } Session::~Session() { + // TODO + if (this->isConnected()) { this->disconnect(); } @@ -134,6 +138,10 @@ void Session::openConnection(const char *addr, int port, const char *username, c throw SessionConnectException(fmt::format("Failed to authenticate: {}", error)); } + do { + this->sftpSession = libssh2_sftp_init(this->session); + } while (this->sftpSession == nullptr && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN); + do { this->channel = libssh2_channel_open_session(session); } while (this->channel == nullptr && libssh2_session_last_errno(this->session) == LIBSSH2_ERROR_EAGAIN); @@ -154,6 +162,8 @@ void Session::openConnection(const char *addr, int port, const char *username, c do { rc = libssh2_channel_shell(this->channel); } while (rc == LIBSSH2_ERROR_EAGAIN); + + } void Session::disconnect() { diff --git a/src/term_windows.cpp b/src/term_windows.cpp index fe0a9e5..f189392 100644 --- a/src/term_windows.cpp +++ b/src/term_windows.cpp @@ -2,8 +2,7 @@ // Created by oupson on 28/02/2022. // -#include "vte.hpp" -#include "term_windows.hpp" + #include #include @@ -13,7 +12,11 @@ #include #include -FooTermWindow::FooTermWindow() { +#include "term_windows.hpp" +#include "vte.hpp" +#include "panel.hpp" + +FooTermWindow::FooTermWindow() : eventLoop(this) { this->set_default_size(800, 600); this->add(notebook); @@ -105,12 +108,16 @@ void FooTermWindow::on_button_click() { } try { - Vte *vte = new Vte(); - vte->spawnShell(this->eventLoop, host.c_str(), port, username.c_str(), password.c_str()); + auto *panel = new Panel( + this->eventLoop, + host.c_str(), + port, + username.c_str(), + password.c_str() + ); - Gtk::Widget *widget = vte->asGtkWidget(); - this->notebook.append_page(*widget, host, true); - widget->show(); + this->notebook.append_page(panel->getPaned(), host, true); + panel->getPaned().show_all(); } catch (std::exception &e) { std::cerr << e.what() << std::endl; } diff --git a/src/vte.cpp b/src/vte.cpp index bce9921..d19c523 100644 --- a/src/vte.cpp +++ b/src/vte.cpp @@ -7,11 +7,10 @@ #include "ssh.hpp" #include -#include // std::thread #include #include -static void got_child_exited([[maybe_unused]] VteTerminal *vte, gint status, [[maybe_unused]] Vte *window) { +static void got_child_exited([[maybe_unused]] VteTerminal *vte, [[maybe_unused]] gint status, [[maybe_unused]] Vte *window) { // TODO WHEN LOCAL TERMINAL } @@ -29,10 +28,13 @@ termStateCallback([[maybe_unused]] VteTerminal *terminal, GPid pid, GError *erro Vte::Vte() { this->terminal = vte_terminal_new(); + +#if VTE_CHECK_VERSION(0, 62, 0) vte_terminal_set_enable_sixel(VTE_TERMINAL(this->terminal), true); +#endif } -void Vte::spawnShell(EventLoop &eventLoop, const char *host, int port, const char *username, const char *password) { +void Vte::spawnShell(EventLoop &eventLoop, Session& session) { int master, slave; struct termios settings{}; @@ -45,21 +47,19 @@ void Vte::spawnShell(EventLoop &eventLoop, const char *host, int port, const cha VtePty *pty = vte_pty_new_foreign_sync(master, nullptr, nullptr); vte_terminal_set_pty(VTE_TERMINAL(this->terminal), pty); - auto *s = new Session(); - s->openConnection(host, port, username, password); EventLoopEntry entry1{}; - entry1.out = slave; + entry1.fdout = slave; entry1.entryType = EventLoopEntry::CHANNEL; - entry1.channel = s->getChannel(); + entry1.channel = session.getChannel(); - eventLoop.registerFd(s->getSock(), entry1); + eventLoop.registerFd(session.getSock(), entry1); EventLoopEntry entry2{}; - entry2.out = slave; + entry2.fdout = slave; entry2.entryType = EventLoopEntry::DESCRIPTOR; - entry2.channel = s->getChannel(); + entry2.channel = session.getChannel(); eventLoop.registerFd(slave, entry2);