Commit 38e977a7 authored by Melroy van den Berg's avatar Melroy van den Berg
Browse files

Merge branch '15-publish-content-to-ipfs' into 'master'

Resolve "Publish content to IPFS"

Closes #15

See merge request libreweb/browser!10
parents 33785aba 59a4be25
Pipeline #3171 passed with stages
in 50 seconds
......@@ -58,7 +58,7 @@ I'm using VSCodium editor, with the following extensions installed: `C/C++`, `CM
For the build you need at least:
* GCC 9 or higher (GCC 8 should also work, but not adviced. Package: `build-essential`)
* GCC 9 or higher (`build-essential`, `g++-9`)
* CMake (Package: `cmake`)
* Ninja build system (Package: `ninja-build`)
* GTK & Pango (including C++ bindings):
......
......@@ -74,23 +74,25 @@ set(SCHEMA_FILE ${CMAKE_CURRENT_SOURCE_DIR}/org.libreweb.browser.gschema.xml)
# Source code
set(HEADERS
about.h
draw.h
file.h
ipfs-process.h
ipfs.h
mainwindow.h
menu.h
md-parser.h
draw.h
menu.h
source-code-dialog.h
)
set(SOURCES
main.cc
about.cc
draw.cc
file.cc
ipfs-process.cc
ipfs.cc
mainwindow.cc
menu.cc
md-parser.cc
draw.cc
menu.cc
source-code-dialog.cc
${HEADERS}
)
......
......@@ -261,7 +261,7 @@ std::string Draw::getText()
*/
void Draw::setText(const std::string &content)
{
DispatchData *data = g_new0(struct DispatchData, 1);
DispatchData *data = new DispatchData();
data->buffer = buffer;
data->text = content;
gdk_threads_add_idle((GSourceFunc)insertPlainTextIdle, data);
......@@ -765,7 +765,6 @@ void Draw::disableEdit()
this->deleteTextSignalHandler.disconnect();
}
/**
* Search for links
*/
......@@ -1187,20 +1186,12 @@ void Draw::insertText(std::string text, const std::string &url, CodeTypeEnum cod
}
}
/**
* Insert plain text - thread safe
*/
void Draw::insertPlainText(const std::string &content)
{
}
/**
* Insert url link - thread safe
*/
void Draw::insertLink(const std::string &text, const std::string &url, const std::string &urlFont)
{
DispatchData *data = g_new0(struct DispatchData, 1);
DispatchData *data = new DispatchData();
data->buffer = buffer;
data->text = text;
data->url = url;
......@@ -1213,7 +1204,7 @@ void Draw::insertLink(const std::string &text, const std::string &url, const std
*/
void Draw::truncateText(int charsTruncated)
{
DispatchData *data = g_new0(struct DispatchData, 1);
DispatchData *data = new DispatchData();
data->buffer = buffer;
data->charsTruncated = charsTruncated;
gdk_threads_add_idle((GSourceFunc)truncateTextIdle, data);
......@@ -1251,7 +1242,7 @@ void Draw::encodeText(std::string &string)
*/
void Draw::insertMarkupTextOnThread(const std::string &text)
{
DispatchData *data = g_new0(struct DispatchData, 1);
DispatchData *data = new DispatchData();
data->buffer = buffer;
data->text = text;
gdk_threads_add_idle((GSourceFunc)insertTextIdle, data);
......@@ -1316,7 +1307,6 @@ gboolean Draw::insertTextIdle(struct DispatchData *data)
*/
gboolean Draw::insertPlainTextIdle(struct DispatchData *data)
{
GtkTextIter end_iter;
gtk_text_buffer_set_text(data->buffer, data->text.c_str(), -1);
g_free(data);
return FALSE;
......
......@@ -125,7 +125,6 @@ private:
void processNode(cmark_node *node, cmark_event_type ev_type);
// Helper functions for inserting text (thread-safe)
void insertText(std::string text, const std::string &url = "", CodeTypeEnum codeType = CodeTypeEnum::NONE);
void insertPlainText(const std::string &content);
void insertLink(const std::string &text, const std::string &url, const std::string &urlFont = "");
void truncateText(int charsTruncated);
void encodeText(std::string &string);
......
......@@ -2,8 +2,6 @@
#include <ipfs/client.h>
#include <stdexcept>
#include <fstream>
#include <sstream>
#include <iostream>
#ifdef LEGACY_CXX
#include <experimental/filesystem>
......@@ -52,32 +50,6 @@ void File::write(const std::string &path, const std::string &content)
file.close();
}
/**
* \brief Fetch file from IFPS network (create a new client connection for thread safety)
* \param path File path
* \throw runtime error when something goes wrong
* \return content as string
*/
std::string const File::fetch(const std::string &path)
{
ipfs::Client client("localhost", 5001, "6s");
std::stringstream contents;
client.FilesGet(path, &contents);
return contents.str();
}
/**
* \brief Publish file to IPFS network (does *not* need to be thead-safe, but is thread-safe nevertheless now)
* \param filename Filename that gets stored in IPFS
* \param content Content that needs to be written to the IPFS network
* \return IPFS content-addressed identifier (CID)
*/
std::string const File::publish(const std::string &filename, const std::string &content)
{
// TODO: Publish file to IPFS
return "CID";
}
/**
* \brief Retrieve filename from file path
* \param path Full path
......
......@@ -5,15 +5,13 @@
/**
* \class File
* \brief Fetch markdown file from disk or the IPFS network
* \brief Read/write markdown files from disk and retrieve filename from path
*/
class File
{
public:
static std::string const read(const std::string &path);
static void write(const std::string &path, const std::string &content);
static std::string const fetch(const std::string &path);
static std::string const publish(const std::string &filename, const std::string &content);
static std::string const getFilename(const std::string &path);
};
#endif
\ No newline at end of file
#include "ipfs-process.h"
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <iostream>
#include <string.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
#ifdef LEGACY_CXX
#include <experimental/filesystem>
namespace n_fs = ::std::experimental::filesystem;
#else
#include <filesystem>
namespace n_fs = ::std::filesystem;
#endif
/**
* \brief Start IPFS Daemon in the background via fork() see: main.cc
* \return exit code
*/
int IPFSProcess::startIPFSDaemon()
{
// Kill any running IPFS daemons if needed
if (IPFSProcess::shouldKillRunningProcess())
{
std::cout << "INFO: Already running ipfs process will be terminated." << std::endl;
int res = std::system("killall -w -q ipfs");
if (res != 0)
{
// ignore
}
}
// Find the IPFS binary
std::string executable = IPFSProcess::findIPFSBinary();
std::cout << "INFO: Starting IPFS Daemon, using: " << executable << std::endl;
if (n_fs::exists(executable))
{
/// open /dev/null for writing
int fd = open("/dev/null", O_WRONLY);
dup2(fd, 1); // make stdout a copy of fd (> /dev/null)
dup2(fd, 2); // ..and same with stderr
close(fd); // close fd
// stdout and stderr now write to /dev/null
// Ready to call exec to start IPFS Daemon
const char *exe = executable.c_str();
char *proc[] = {strdup(exe), strdup("daemon"), strdup("--init"), strdup("--migrate"), NULL};
return execv(exe, proc);
}
else
{
std::cerr << "Error: IPFS Daemon is not found. IPFS will not work!" << std::endl;
return -1;
}
}
/**
* \brief Determine if the app needs to kill any running IPFS process
* \return true if it needs to be terminated, otherwise false
*/
bool IPFSProcess::shouldKillRunningProcess()
{
FILE *cmd_pipe = popen("pidof -s ipfs", "r");
if (cmd_pipe != NULL)
{
char pidbuf[512];
memset(pidbuf, 0, sizeof(pidbuf));
if (fgets(pidbuf, 512, cmd_pipe) == NULL)
{
//ignore
}
pclose(cmd_pipe);
if (strlen(pidbuf) > 0)
{
pid_t pid = strtoul(pidbuf, NULL, 10);
char pathbuf[1024];
memset(pathbuf, 0, sizeof(pathbuf));
std::string path = "/proc/" + std::to_string(pid) + "/exe";
if (readlink(path.c_str(), pathbuf, sizeof(pathbuf) - 1) > 0)
{
char beginPath[] = "/usr/share/libreweb-browser";
// If the begin path does not path (!= 0), return true,
// meaning the process will be killed.
return (strncmp(pathbuf, beginPath, strlen(beginPath)) != 0);
}
// TODO: Compare IPFS version as well (via: "ipfs version" command), maybe?
}
else
{
// No running IPFS process
return false;
}
}
// Something went wrong, fallback is to kill (better safe then sorry)
return true;
}
/**
* \brief Try to find the binary location of ipfs (IPFS go server)
* \return full path to the ipfs binary, empty string when not found
*/
std::string IPFSProcess::findIPFSBinary()
{
// Try absolute path first
for (std::string data_dir : Glib::get_system_data_dirs())
{
std::vector<std::string> path_builder{data_dir, "libreweb-browser", "go-ipfs", "ipfs"};
std::string ipfs_binary_path = Glib::build_path(G_DIR_SEPARATOR_S, path_builder);
if (Glib::file_test(ipfs_binary_path, Glib::FileTest::FILE_TEST_IS_EXECUTABLE))
{
return ipfs_binary_path;
}
}
// Try local path if the images are not installed (yet)
// When working directory is in the build/bin folder (relative path)
std::string currentPath = n_fs::current_path().string();
std::string ipfs_binary_path = Glib::build_filename(currentPath, "../..", "go-ipfs", "ipfs");
if (Glib::file_test(ipfs_binary_path, Glib::FileTest::FILE_TEST_IS_EXECUTABLE))
{
return ipfs_binary_path;
}
else
{
return "";
}
}
\ No newline at end of file
#ifndef IPFS_PROCESS_H
#define IPFS_PROCESS_H
#include <string>
/**
* \class IPFSProcess
* \brief Helper class to start/stop IPFS deamon, all static methods
*/
class IPFSProcess
{
public:
static int startIPFSDaemon();
private:
static bool shouldKillRunningProcess();
static std::string findIPFSBinary();
};
#endif
\ No newline at end of file
#include "ipfs.h"
#include <cstdlib>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
#ifdef LEGACY_CXX
#include <experimental/filesystem>
namespace n_fs = ::std::experimental::filesystem;
#else
#include <filesystem>
namespace n_fs = ::std::filesystem;
#endif
#include <sstream>
/**
* \brief IPFS Contructor, connect to IPFS
......@@ -66,115 +49,38 @@ std::map<std::string, float> IPFS::getBandwidthRates()
}
/**
* \brief Start IPFS Daemon
* \return exit code
* \brief Fetch file from IFPS network (create a new client object each time - which is thread-safe), static method
* \param path File path
* \throw std::runtime_error when there is a connection-time/something goes wrong while trying to get the file
* \return content as string
*/
int IPFS::startIPFSDaemon()
std::string const IPFS::fetch(const std::string &path)
{
// Kill any running IPFS daemons if needed
if (IPFS::shouldKillRunningProcess())
{
std::cout << "INFO: Already running ipfs process will be terminated." << std::endl;
int res = std::system("killall -w -q ipfs");
if (res != 0)
{
// ignore
}
}
// Find the IPFS binary
std::string executable = IPFS::findIPFSBinary();
std::cout << "INFO: Starting IPFS Daemon, using: " << executable << std::endl;
if (n_fs::exists(executable))
{
/// open /dev/null for writing
int fd = open("/dev/null", O_WRONLY);
dup2(fd, 1); // make stdout a copy of fd (> /dev/null)
dup2(fd, 2); // ..and same with stderr
close(fd); // close fd
// stdout and stderr now write to /dev/null
// Ready to call exec to start IPFS Daemon
const char *exe = executable.c_str();
char *proc[] = {strdup(exe), strdup("daemon"), strdup("--init"), strdup("--migrate"), NULL};
return execv(exe, proc);
}
else
{
std::cerr << "Error: IPFS Daemon is not found. IPFS will not work!" << std::endl;
return -1;
}
ipfs::Client client("localhost", 5001, "6s");
std::stringstream contents;
client.FilesGet(path, &contents);
return contents.str();
}
/**
* \brief Determine if the app needs to kill any running IPFS process
* \return true if it needs to be terminated, otherwise false
* \brief Add a file to IPFS network (not thread-safe)
* \param path File path where the file could be stored in IPFS (like puting a file inside a directory within IPFS)
* \param content Content that needs to be written to the IPFS network
* \throw std::runtime_error when there is a connection-time/something goes wrong while trying to get the file
* \return IPFS content-addressed identifier (CID) hash
*/
bool IPFS::shouldKillRunningProcess()
std::string const IPFS::add(const std::string &path, const std::string &content)
{
FILE *cmd_pipe = popen("pidof -s ipfs", "r");
if (cmd_pipe != NULL)
ipfs::Json result;
// Publish a single file
client.FilesAdd({{path, ipfs::http::FileUpload::Type::kFileContents, content}}, &result);
if (result.is_array())
{
char pidbuf[512];
memset(pidbuf, 0, sizeof(pidbuf));
if (fgets(pidbuf, 512, cmd_pipe) == NULL)
for (const auto &files : result.items())
{
//ignore
}
pclose(cmd_pipe);
if (strlen(pidbuf) > 0)
{
pid_t pid = strtoul(pidbuf, NULL, 10);
char pathbuf[1024];
memset(pathbuf, 0, sizeof(pathbuf));
std::string path = "/proc/" + std::to_string(pid) + "/exe";
if (readlink(path.c_str(), pathbuf, sizeof(pathbuf) - 1) > 0)
{
char beginPath[] = "/usr/share/libreweb-browser";
// If the begin path does not path (!= 0), return true,
// meaning the process will be killed.
return (strncmp(pathbuf, beginPath, strlen(beginPath)) != 0);
}
// TODO: Compare IPFS version as well, maybe?
}
else
{
// No running IPFS process
return false;
return files.value()["hash"];
}
}
// Something went wrong, fallback is to kill (better safe then sorry)
return true;
// something is wrong, fallback
return "";
}
/**
* \brief Try to find the binary location of ipfs (IPFS go server)
* \return full path to the ipfs binary, empty string when not found
*/
std::string IPFS::findIPFSBinary()
{
// Try absolute path first
for (std::string data_dir : Glib::get_system_data_dirs())
{
std::vector<std::string> path_builder{data_dir, "libreweb-browser", "go-ipfs", "ipfs"};
std::string ipfs_binary_path = Glib::build_path(G_DIR_SEPARATOR_S, path_builder);
if (Glib::file_test(ipfs_binary_path, Glib::FileTest::FILE_TEST_IS_EXECUTABLE))
{
return ipfs_binary_path;
}
}
// Try local path if the images are not installed (yet)
// When working directory is in the build/bin folder (relative path)
std::string currentPath = n_fs::current_path().string();
std::string ipfs_binary_path = Glib::build_filename(currentPath, "../..", "go-ipfs", "ipfs");
if (Glib::file_test(ipfs_binary_path, Glib::FileTest::FILE_TEST_IS_EXECUTABLE))
{
return ipfs_binary_path;
}
else
{
return "";
}
}
\ No newline at end of file
......@@ -6,20 +6,18 @@
/**
* \class IPFS
* \brief Helper class to start/stop IPFS deamon and other IPFS calls
* \brief Start IPFS connection and contain IPFS related calls
*/
class IPFS
{
public:
explicit IPFS(const std::string &host, int port);
static int startIPFSDaemon();
std::size_t getNrPeers();
std::map<std::string, float> getBandwidthRates();
static std::string const fetch(const std::string &path);
std::string const add(const std::string &path, const std::string &content);
private:
ipfs::Client client;
static bool shouldKillRunningProcess();
static std::string findIPFSBinary();
};
#endif
\ No newline at end of file
#include "mainwindow.h"
#include "ipfs.h"
#include "ipfs-process.h"
#include "project_config.h"
#include <iostream>
#include <gtkmm/application.h>
......@@ -30,7 +30,7 @@ int main(int argc, char *argv[])
if (child_pid == 0)
{
// Run by child process
return IPFS::startIPFSDaemon();
return IPFSProcess::startIPFSDaemon();
}
else if (child_pid > 0)
{
......
......@@ -79,6 +79,7 @@ MainWindow::MainWindow()
m_menu.new_doc.connect(sigc::mem_fun(this, &MainWindow::new_doc)); /*!< Menu item for new document */
m_menu.open.connect(sigc::mem_fun(this, &MainWindow::open)); /*!< Menu item for opening existing document */
m_menu.open_edit.connect(sigc::mem_fun(this, &MainWindow::open_and_edit)); /*!< Menu item for opening & editing existing document */
m_menu.edit.connect(sigc::mem_fun(this, &MainWindow::edit)); /*!< Menu item for editing current open document */
m_menu.save.connect(sigc::mem_fun(this, &MainWindow::save)); /*!< Menu item for save document */
m_menu.save_as.connect(sigc::mem_fun(this, &MainWindow::save_as)); /*!< Menu item for save document as */
m_menu.publish.connect(sigc::mem_fun(this, &MainWindow::publish)); /*!< Menu item for publishing */
......@@ -641,7 +642,7 @@ void MainWindow::selectAll()
}
/**
* Trigger/creating a new document
* \brief Trigger when user selected 'new document' from menu item
*/
void MainWindow::new_doc()
{
......@@ -657,6 +658,9 @@ void MainWindow::new_doc()
this->set_title("Untitled * - " + m_appName);
}
/**
* \brief Triggered when user selected 'open...' from menu item / toolbar
*/
void MainWindow::open()
{
auto dialog = new Gtk::FileChooserDialog("Open", Gtk::FILE_CHOOSER_ACTION_OPEN);
......@@ -680,6 +684,9 @@ void MainWindow::open()
dialog->show();
}
/**
* \brief Triggered when user selected 'open & edit...' from menu item
*/
void MainWindow::open_and_edit()
{
auto dialog = new Gtk::FileChooserDialog("Open & Edit", Gtk::FILE_CHOOSER_ACTION_OPEN);
......@@ -703,6 +710,9 @@ void MainWindow::open_and_edit()
dialog->show();
}
/**
* \brief Signal response when 'open' dialog is closed
*/
void MainWindow::on_open_dialog_response(int response_id, Gtk::FileChooserDialog *dialog)
{
switch (response_id)
......@@ -731,21 +741,24 @@ void MainWindow::on_open_dialog_response(int response_id, Gtk::FileChooserDialog
delete dialog;
}
/**
* \brief Signal response when 'open & edit' dialog is closed
*/
void MainWindow::on_open_edit_dialog_response(int response_id, Gtk::FileChooserDialog *dialog)
{
switch (response_id)
{
case Gtk::ResponseType::RESPONSE_OK:
{
// Enable editor if needed
if (!this->isEditorEnabled())
this->enableEdit();
auto filePath = dialog->get_file()->get_path();
std::string path = "file://" + filePath;
// Open file and set address bar, but do not parse the content or the disable editor
doRequest(path, true, false, false, false);
// Enable editor if needed
if (!this->isEditorEnabled())
this->enableEdit();
// Change address bar
this->m_addressBar.set_text(path);
// Set new title
......@@ -768,6 +781,22 @@ void MainWindow::on_open_edit_dialog_response(int response_id, Gtk::FileChooserD
delete dialog;
}
/**
* \brief Triggered when user selected 'edit' from menu item
*/
void MainWindow::edit()
{
if (!this->isEditorEnabled())
this->enableEdit();
m_draw_main.setText(this->currentContent);
// Set title
this->set_title("Untitled * - " + m_appName);
}
/**
* \brief Triggered when user selected 'save' from menu item / toolbar
*/
void MainWindow::save()
{
if <