Selaa lähdekoodia

Merge branch 'cleanup-cli' into 'develop'

US17: Cleanup of CLI

See merge request tobias.wach/ccats!23
Serdyukov, Denys 5 vuotta sitten
vanhempi
commit
dbae13f857

+ 2 - 1
cli/CMakeLists.txt

@@ -4,7 +4,8 @@ cmake_minimum_required(VERSION 2.8)
 
 project(ccats-cli)
 
-add_executable(ccats-cli src/main.cpp src/iomanager.cpp src/machineiomanager.cpp src/useriomanager.cpp src/commands.cpp src/base64.cpp)
+# add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/base64.cpp src/netman.cpp src/cmdman.cpp src/fileman.cpp)
+add_executable(ccats-cli src/main.cpp src/ioman.cpp src/machineioman.cpp src/userioman.cpp src/base64.cpp src/cmdman.cpp src/fileman.cpp)
 
 # use pkg-config to find readline as it doesnt provide cmake files
 find_package(PkgConfig REQUIRED)

+ 0 - 0
cli/include/base64.hpp → cli/include/base64.h


+ 134 - 0
cli/include/cmdman.h

@@ -0,0 +1,134 @@
+#ifndef CMDMAN_H
+#define CMDMAN_H
+
+#include "fileman.h"
+
+#include <json/json.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+using std::map;
+using std::string;
+using std::vector;
+
+/**
+ * @class CmdMan
+ *
+ * A class that provides handling of user provided commands as well as
+ * commands as provided by json responses from a server.
+ */
+class CmdMan {
+public:
+  /**
+   * Type of message returned in CmdRet.
+   * notsend	- do not send anything to the server
+   * send		- send something to the server
+   * error	- an error occured, do not send to the server
+   * close	- terminate the connection
+   * seton	- contextually change state, used for version check and login
+   */
+  enum rettype { notsend, send, error, close, seton };
+  /**
+   * Response to user or command input
+   *
+   * string is to be handled depending on type
+   */
+  struct CmdRet {
+    rettype type;
+    string msg;
+  };
+  /**
+   * Constructor and destructor
+   */
+  CmdMan(FileMan &fm);
+  ~CmdMan();
+
+  /**
+   * Executes a user provided command with optional arguments
+   */
+  CmdRet execute(string cmd, vector<string> args);
+  /**
+   * Handles a server provided response json
+   */
+  CmdRet handle(Json::Value root);
+
+  /**
+   * Internal json reader
+   */
+  Json::CharReader *reader;
+
+private:
+  /**
+   * internal json writer and error string member
+   */
+  Json::StreamWriterBuilder wbuilder;
+  string jsonerror;
+
+  /**
+   * FileMan instance used to file commands
+   */
+  FileMan &fileman;
+
+  /**
+   * Maps containing pointers to the appropriate member functions for executing
+   * or handling commands
+   */
+  map<string, CmdRet (CmdMan::*)(vector<string>)> execmap;
+  map<string, CmdRet (CmdMan::*)(Json::Value)> handlemap;
+  /**
+   * Map containing help strings to show to a user
+   */
+  map<string, string> helpmap;
+
+  /**
+   * State used to internally format received json to allow easy handling using
+   * handlemap
+   */
+  bool dologin, doversion;
+
+  /**
+   * Help strings and method prototypes for commands to be used by a user
+   */
+  /* execute command descriptions and methods go here */
+  const string descHelp = "print available commands";
+  CmdRet cmdHelp(vector<string> args);
+  const string descStatus = "request status from server";
+  CmdRet cmdStatus(vector<string> args);
+  const string descDisconnect = "disconnect from server";
+  CmdRet cmdDisconnect(vector<string> args);
+  const string descPut = "upload file to server and add to queue";
+  CmdRet cmdPut(vector<string> args);
+  const string descGet = "retrieve file from server";
+  CmdRet cmdGet(vector<string> args);
+  const string descList = "list files available on server";
+  CmdRet cmdList(vector<string> args);
+
+  /**
+   * Method prototypes for commands used internally
+   */
+  /* internal execute commands */
+  CmdRet cmdVersion(vector<string> args);
+  CmdRet cmdLogin(vector<string> args);
+  CmdRet cmdPutdata(vector<string> args);
+  CmdRet cmdGetdata(vector<string> args);
+  CmdRet cmdListdata(vector<string> args);
+
+  /**
+   * Method prototypes for handling json responses
+   */
+  /* handle commands go here */
+  CmdRet handleStatus(Json::Value);
+  CmdRet handleClose(Json::Value);
+  CmdRet handlePut(Json::Value);
+  CmdRet handleGet(Json::Value);
+  CmdRet handleList(Json::Value);
+  CmdRet handlePutdata(Json::Value);
+  CmdRet handleGetdata(Json::Value);
+  CmdRet handleListdata(Json::Value);
+  CmdRet handleVersion(Json::Value);
+  CmdRet handleLogin(Json::Value);
+};
+
+#endif

+ 0 - 37
cli/include/commands.hpp

@@ -1,37 +0,0 @@
-#ifndef COMMANDS_HPP
-#define COMMANDS_HPP
-
-#include <cstring>
-#include <cctype>
-
-#define COMMANDLEN 10
-#define sizeofarr(a) (sizeof(a) / sizeof(a[0]))
-
-typedef enum {
-  CMD_HELP,
-  CMD_CONNECT,
-  CMD_DISCONNECT,
-  CMD_PUT,
-  CMD_REMOVE,
-  CMD_GET,
-  CMD_LIST,
-  CMD_STATUS,
-  CMD_SETUP,
-  CMD_LOG,
-  CMD_UNKNOWN
-} COMMANDID;
-
-typedef struct {
-  COMMANDID cmd;
-  const char *name;
-  const char *desc;
-} CMD;
-
-extern CMD commands[];
-
-COMMANDID getCmdIdFromString(const char *str);
-const char *getCmdStringFromId(COMMANDID id);
-// TODO
-void printCmds(void);
-
-#endif

+ 130 - 0
cli/include/fileman.h

@@ -0,0 +1,130 @@
+#ifndef FILEMAN_H
+#define FILEMAN_H
+
+#include <fstream>
+#include <string>
+#include <vector>
+
+#define BLOCKSIZE 8
+
+/**
+ * @class FileMan
+ *
+ * Provides File I/O abstraction
+ */
+class FileMan {
+private:
+  /**
+   * Internal state
+   *
+   * Filestreams for put and get
+   * Vector for holding received filenames from listing
+   * Names for put and get
+   * Size for internal read
+   * Total and Remaining chunks for put, get and list
+   * Boolean replacement for filestreams being open for list
+   *
+   */
+  std::ifstream putfile;
+  std::ofstream getfile;
+  std::vector<std::string> listdata;
+  std::string getname, putname;
+  const unsigned int max_read_len = 8;
+  int putsize;
+  int putchunks;
+  int putchunksRemaining;
+  int getchunks;
+  int getchunksRemaining;
+  int listchunks;
+  int listchunksRemaining;
+
+  bool islisting;
+
+public:
+  /**
+   * Constructor and Destructor
+   */
+  FileMan();
+  ~FileMan();
+
+  /**
+   * Query internal state
+   *
+   * Return true if the corresponding action is being performed, false otherwise
+   */
+  bool isGetting();
+  bool isPutting();
+  bool isListing();
+
+  /**
+   * Check for and prepare state and streams for reading/writing
+   *
+   * Return true if successful, false otherwise
+   */
+  bool openPut(const std::string &name);
+  bool openGet(const std::string &name);
+  bool openList();
+
+  /**
+   * Close the respective filestream
+   */
+  void closePut();
+  void closeGet();
+  void closeList();
+
+  /**
+   * Query the names of the file currently being put or get
+   */
+  std::string getPutName();
+  std::string getGetName();
+
+  /**
+   * Cancel a put, get or list, depreparing internal state (closing streams if
+   * required)
+   */
+  void cancelPut();
+  void cancelGet();
+  void cancelList();
+
+  /**
+   * Read max_rea_len bytes from the current file opened for put
+   */
+  std::vector<char> readPut();
+  /**
+   * Write the provided vector to the current file opened for get
+   */
+  void writeGet(std::vector<char> data);
+
+  /**
+   * Wrapper methods for reading and writing base64 encoded data instead of raw
+   * bytes
+   */
+  std::string readBase64();
+  void writeBase64(std::string data);
+
+  /**
+   * read and write emulating methods for list
+   */
+  void putListData(std::vector<std::string> names);
+  std::vector<std::string> getListData();
+
+  /**
+   * Query internal state, requesting the corresponding size
+   */
+  int getPutChunks();
+  int getGetChunks();
+  int getListChunks();
+  int getPutRemainingChunks();
+  int getGetRemainingChunks();
+  int getListRemainingChunks();
+  int getPutSize();
+
+  /**
+   * Set internal state, adjusting the chunks as well as chunks remaining for
+   * get and list
+   */
+  void setGetChunks(int chunks);
+  void setListChunks(int chunks);
+};
+
+#endif

+ 8 - 0
cli/include/global.h

@@ -0,0 +1,8 @@
+#ifndef GLOBAL_H
+#define GLOBAL_H
+
+#include <string>
+
+const std::string protocolVersion = "0.2";
+
+#endif

+ 0 - 6
cli/include/global.hpp

@@ -1,6 +0,0 @@
-#ifndef GLOBAL_HPP
-#define GLOBAL_HPP
-
-#define VERSION "0.1"
-
-#endif

+ 154 - 0
cli/include/ioman.h

@@ -0,0 +1,154 @@
+#ifndef IOMAN_H
+#define IOMAN_H
+
+#include "cmdman.h"
+#include "fileman.h"
+
+#include <boost/asio.hpp>
+#include <condition_variable>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <vector>
+
+using boost::asio::ip::tcp;
+
+/**
+ * @class IoMan
+ *
+ * Input/Output manager
+ *
+ * Provides the glue logic for getting user and network input
+ * as well as providing user and network output
+ */
+class IoMan {
+  /* this is technically private and protected stuff which needs to be public
+   * for the readline callback */
+public:
+  /**
+   * Type of message to be output
+   */
+  enum OutMsgType { normal, error, debug };
+  /**
+   * Output msg to the user, treating it as type
+   */
+  virtual void printMessage(std::string msg, OutMsgType type);
+  /**
+   * Flag wether to keep the main thread running
+   * Matching mutex to provide syncthonizes access
+   */
+  bool runmain;
+  std::mutex mainmutex;
+  /**
+   * Vector to hold input generated/fetched locally (e.g. from the user)
+   * Matching condition variable to wake up waiting threads
+   * Matching mutex to provide synchronized access
+   */
+  std::mutex localmutex;
+  std::vector<std::string> localinput;
+  std::condition_variable localcv;
+
+private:
+  /**
+   * Internal state to provide class-wide asio networking functionality
+   */
+  boost::asio::io_service ios;
+  boost::asio::streambuf recvbuf;
+  boost::system::error_code errcode;
+  tcp::socket *tcpsock;
+  /**
+   * The IP and port to connect to
+   * Flag telling wether one is connected
+   */
+  std::string ipstring;
+  int port;
+  bool connected;
+  /**
+   * Instances of CmdMan and FileMan to process user input and handle File I/O
+   */
+  CmdMan cmdman;
+  FileMan fileman;
+
+  /**
+   * Thread handles for processing local and network input as well as generating
+   * responses to both Matching mutexes for the flags wether the threads should
+   * keep running Function prototypes for the main thread functions
+   */
+  std::thread tinput, tnetwork, tresponse;
+  std::mutex inputmutex, networkmutex, responsemutex;
+  bool runinput, runnetwork, runresponse;
+  void networkMain();
+  void inputMain();
+  void responseMain();
+
+  /**
+   * Vector to hold preprocessed input from the network
+   * Matching condition variable to wake up waiting threads
+   * Matching mutex to provide synchronized access
+   */
+  std::vector<Json::Value> netinput;
+  std::mutex netmutex;
+  std::condition_variable netcv;
+
+  /**
+   * Class-wide json functionality
+   */
+  Json::CharReader *reader;
+  Json::StreamWriterBuilder wbuilder;
+  string jsonerror;
+
+  /**
+   * Enum for internal login and version state
+   * Variables for keeping said state
+   * Matching mutex and condition variable
+   */
+  enum Status { off, on, err };
+  Status versionstatus;
+  Status loginstatus;
+  std::mutex initmutex;
+  std::condition_variable initcv;
+
+protected:
+  /**
+   * mutex
+   */
+  std::mutex msgmutex;
+
+  /**
+   * Prompt messages for readline prompt
+   */
+  virtual void printWelcomeMessage() = 0;
+  virtual std::string getCmdPrompt() = 0;
+  virtual std::string getUserPrompt() = 0;
+  virtual std::string getPassPrompt() = 0;
+
+public:
+  /**
+   * Constructor and destructor
+   */
+  IoMan(char *ipcstring);
+  ~IoMan();
+
+  /**
+   * Establish connection to server and perform vesion check
+   * Return true if successful, false otherwise
+   */
+  bool init();
+
+  /**
+   * Main loop, call init first
+   */
+  void run();
+
+  /**
+   * Establish connection to server
+   * Return true if successful, false otherwise
+   */
+  bool connect();
+  /**
+   * Disconnect from a connected server
+   */
+  void disconnect();
+};
+
+#endif

+ 0 - 42
cli/include/iomanager.hpp

@@ -1,42 +0,0 @@
-#ifndef IOMANAGER_HPP
-#define IOMANAGER_HPP
-
-#include "global.hpp"
-
-#include <string>
-#include <boost/asio.hpp>
-#include <json/json.h>
-
-#define BLOCKSIZE 8
-
-using boost::asio::ip::tcp;
-
-class IoManager {
-protected:
-	boost::asio::io_service ios;
-	tcp::socket *tcpsock;
-	boost::system::error_code errcode;
-	std::string ipstring;
-	int port;
-
-	std::string jsonerror;
-
-	Json::CharReaderBuilder rbuilder;
-	Json::CharReader* reader;
-	Json::StreamWriterBuilder wbuilder;
-
-	bool sendJson(Json::Value root);
-	bool receiveJson(boost::asio::streambuf &recvbuf);
-	bool parseJson(Json::Value *root, boost::asio::streambuf &recvbuf);
-public:
-	// Basic constructor
-	IoManager(char *ipcstring);
-	// destructor to clean up all generic stuff
-	~IoManager();
-	// enters loop to handle further interaction based on derived class
-	virtual void run() = 0;
-	// tries to establish connection, returns false on error
-	bool connect();
-};
-
-#endif

+ 30 - 0
cli/include/machineioman.h

@@ -0,0 +1,30 @@
+#ifndef MACHINEIOMAN_H
+#define MACHINEIOMAN_H
+
+#include "ioman.h"
+
+/**
+ * @class MachineIoMan
+ *
+ * Provides specific implementations of IoMan outputs and prompts
+ * for interactive non-user sessions
+ */
+class MachineIoMan : public IoMan {
+public:
+  using IoMan::IoMan;
+
+  /**
+   * Specific implementations for printing messages
+   */
+  void printMessage(std::string msg, OutMsgType type);
+  void printWelcomeMessage();
+
+  /**
+   * Return the specific prompt strings for IoMan prompts
+   */
+  std::string getCmdPrompt();
+  std::string getUserPrompt();
+  std::string getPassPrompt();
+};
+
+#endif

+ 0 - 15
cli/include/machineiomanager.hpp

@@ -1,15 +0,0 @@
-#ifndef MACHINEIOMANAGER_HPP
-#define MACHINEIOMANAGER_HPP
-
-#include "iomanager.hpp"
-
-class MachineIoManager : public IoManager {
-
-public:
-	// MachineIoManager(char *ipstring) : IoManager(ipstring) {};
-	using IoManager::IoManager;
-	// enters loop to handle machine based interaction (other frontends)
-	void run();
-};
-
-#endif

+ 30 - 0
cli/include/userioman.h

@@ -0,0 +1,30 @@
+#ifndef USERIOMAN_H
+#define USERIOMAN_H
+
+#include "ioman.h"
+
+/**
+ * @class UserIoMan
+ *
+ * Provides specific implementations of IoMan outputs and prompts
+ * for interactive user sessions
+ */
+class UserIoMan : public IoMan {
+public:
+  using IoMan::IoMan;
+
+  /**
+   * Specific implementations for printing messages
+   */
+  void printMessage(std::string msg, OutMsgType type);
+  void printWelcomeMessage();
+
+  /**
+   * Return the specific prompt strings for IoMan prompts
+   */
+  std::string getCmdPrompt();
+  std::string getUserPrompt();
+  std::string getPassPrompt();
+};
+
+#endif

+ 0 - 15
cli/include/useriomanager.hpp

@@ -1,15 +0,0 @@
-#ifndef USERIOMANAGER_HPP
-#define USERIOMANAGER_HPP
-
-#include "iomanager.hpp"
-
-class UserIoManager : public IoManager {
-
-public:
-	// UserIoManager(char *ipstring) : IoManager(ipstring) {};
-	using IoManager::IoManager;
-	// enters loop to handle user based interaction (from the terminal)
-	void run();
-};
-
-#endif

+ 5 - 4
cli/src/base64.cpp

@@ -1,4 +1,4 @@
-#include "../include/base64.hpp"
+#include "../include/base64.h"
 
 #include <boost/algorithm/string.hpp>
 #include <boost/archive/iterators/base64_from_binary.hpp>
@@ -18,9 +18,10 @@ std::vector<char> base64::decodeVector(const std::string &val) {
   using namespace boost::archive::iterators;
   using It =
       transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::vector<char>(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::vector<char> ret =
+      std::vector<char>(It(std::begin(val)), It(std::end(val)));
+  ret.pop_back();
+  return ret;
 }
 
 std::string base64::encode(const std::string &val) {

+ 495 - 0
cli/src/cmdman.cpp

@@ -0,0 +1,495 @@
+#include "../include/cmdman.h"
+#include "../include/global.h"
+
+#include <iostream>
+
+CmdMan::CmdMan(FileMan &fm) : fileman(fm) {
+
+  /* setup json stuff */
+  Json::CharReaderBuilder rbuilder;
+  wbuilder.settings_["indentation"] = "";
+  reader = rbuilder.newCharReader();
+
+  dologin = false;
+  doversion = false;
+
+  /* initialize execute command map */
+  execmap["help"] = &CmdMan::cmdHelp;
+  execmap["status"] = &CmdMan::cmdStatus;
+  execmap["disconnect"] = &CmdMan::cmdDisconnect;
+  execmap["put"] = &CmdMan::cmdPut;
+  execmap["get"] = &CmdMan::cmdGet;
+  execmap["list"] = &CmdMan::cmdList;
+  execmap["version"] = &CmdMan::cmdVersion;
+  execmap["login"] = &CmdMan::cmdLogin;
+  execmap["putdata"] = &CmdMan::cmdPutdata;
+  execmap["getdata"] = &CmdMan::cmdGetdata;
+
+  /* initialize description map */
+  helpmap["help"] = descHelp;
+  helpmap["status"] = descStatus;
+  helpmap["disconnect"] = descDisconnect;
+  helpmap["put"] = descPut;
+  helpmap["get"] = descGet;
+  helpmap["list"] = descList;
+
+  /* initialize handle command map */
+  handlemap["status"] = &CmdMan::handleStatus;
+  handlemap["close"] = &CmdMan::handleClose;
+  handlemap["put"] = &CmdMan::handlePut;
+  handlemap["get"] = &CmdMan::handleGet;
+  handlemap["putdata"] = &CmdMan::handlePutdata;
+  handlemap["getdata"] = &CmdMan::handleGetdata;
+  handlemap["list"] = &CmdMan::handleList;
+  handlemap["version"] = &CmdMan::handleVersion;
+  handlemap["login"] = &CmdMan::handleLogin;
+}
+
+CmdMan::~CmdMan() { delete reader; }
+
+CmdMan::CmdRet CmdMan::cmdHelp(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  map<string, string>::iterator it;
+  for (it = helpmap.begin(); it != helpmap.end(); it++) {
+    retval.msg.append(it->first);
+    retval.msg.append(" - ");
+    retval.msg.append(it->second);
+    retval.msg.append("\n");
+  }
+  retval.type = notsend;
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdStatus(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+  root["command"] = "status";
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdDisconnect(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+  root["command"] = "close";
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdPut(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+
+  bool opened = fileman.openPut(args[0]);
+  if (opened) {
+    root["command"] = "put";
+    root["file"] = fileman.getPutName();
+    root["size"] = fileman.getPutSize();
+    root["chunks"] = fileman.getPutChunks();
+    retval.type = send;
+    retval.msg = Json::writeString(wbuilder, root);
+  } else {
+    retval.type = error;
+    retval.msg = "couldnt open local file \"" + args[0] + "\"";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdPutdata(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+
+  root["command"] = "putdata";
+  root["file"] = fileman.getPutName();
+  root["cancel"] = false;
+  root["data"] = fileman.readBase64();
+  root["remaining"] =
+      fileman
+          .getPutRemainingChunks(); // number already decremented by readBase64
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdGet(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+
+  bool opened = fileman.openGet(args[0]);
+  if (opened) {
+    root["command"] = "get";
+    root["file"] = fileman.getGetName();
+    retval.type = send;
+    retval.msg = Json::writeString(wbuilder, root);
+  } else {
+    retval.type = error;
+    retval.msg = "local file \"" + args[0] + "\" already exists";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdGetdata(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+
+  root["command"] = "getdata";
+  root["file"] = fileman.getGetName();
+  root["chunk"] = fileman.getGetRemainingChunks();
+  root["cancel"] = false;
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdList(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+
+  bool opened = fileman.openList();
+  if (opened) {
+    root["command"] = "list";
+    retval.type = send;
+    retval.msg = Json::writeString(wbuilder, root);
+  } else {
+    retval.type = error;
+    retval.msg = "cannot list, already listing";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdListdata(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+  root["command"] = "listdata";
+  root["chunk"] = fileman.getListRemainingChunks();
+  root["cancel"] = false;
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::execute(string cmd, vector<string> args) {
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  std::cerr << __PRETTY_FUNCTION__ << " using command \"" << cmd
+            << "\" with arguments [ ";
+  for (string s : args)
+    std::cerr << s << " ";
+  std::cerr << "]" << std::endl;
+  map<string, CmdRet (CmdMan::*)(vector<string>)>::iterator it =
+      execmap.find(cmd);
+  string retmsg;
+  if (it == execmap.end()) {
+    return {error, string(__PRETTY_FUNCTION__) + " unknown command \"" + cmd +
+                       "\".\ntype help to list available commands."};
+  }
+  return (this->*(execmap[cmd]))(args);
+}
+
+/* internal commands */
+CmdMan::CmdRet CmdMan::cmdVersion(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+  root["version"] = protocolVersion;
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  doversion = true;
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::cmdLogin(vector<string> args) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  Json::Value root;
+  root["user"] = args[0];
+  root["pass"] = args[1];
+  root["login"] = true;
+  root["cancel"] = false;
+  retval.type = send;
+  retval.msg = Json::writeString(wbuilder, root);
+
+  dologin = true;
+
+  return retval;
+}
+
+/*
+        handlemap["status"] = &CmdMan::handleDefault;
+        handlemap["disconnect"] = &CmdMan::handleDefault;
+        handlemap["put"] = &CmdMan::handleDefault;
+        handlemap["get"] = &CmdMan::handleDefault;
+        handlemap["list"] = NULL;
+        handlemap["version"] = NULL;
+        handlemap["login"] = NULL;
+*/
+
+CmdMan::CmdRet CmdMan::handle(Json::Value root) {
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  if (doversion)
+    root["command"] = "version";
+  else if (dologin)
+    root["command"] = "login";
+  std::cerr << __PRETTY_FUNCTION__ << " using json" << std::endl
+            << root << std::endl;
+  string retmsg;
+  map<string, CmdRet (CmdMan::*)(Json::Value)>::iterator it =
+      handlemap.find(root["command"].asString());
+  if (it == handlemap.end()) {
+    return {error, string(__PRETTY_FUNCTION__) + " unknown command \"" +
+                       root["command"].asString() +
+                       "\".\nensure code is implemented."};
+  }
+  return (this->*(handlemap[root["command"].asString()]))(root);
+}
+
+CmdMan::CmdRet CmdMan::handleStatus(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  retval.type = notsend;
+  retval.msg = root["response"].asString();
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleClose(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  retval.type = close;
+  retval.msg = root["response"].asString();
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handlePut(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    retval.msg = "File upload request failed: Server reports: " +
+                 root["error"].asString();
+  } else if (root["file"].asString() != fileman.getPutName()) {
+    retval.type = error;
+    retval.msg = "File upload request failed: Server reports filename " +
+                 root["file"].asString() + " but actual filename is " +
+                 fileman.getPutName();
+  } else {
+    retval.type = send;
+    retval.msg = "putdata";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handlePutdata(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (root["received"].asInt() != fileman.getPutRemainingChunks()) {
+    // the number of remaining chunks received from the daemon does not equal
+    // the number stored at the client side
+    retval.type = error;
+    retval.msg = std::string("File upload failed: Server reports number of "
+                             "remaining chunks as ") +
+                 std::to_string(root["received"].asInt()) +
+                 " but actual number is " +
+                 std::to_string(fileman.getPutRemainingChunks());
+    fileman.cancelPut();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    retval.msg =
+        "File upload cancelles: Server reports: " + root["error"].asString();
+    fileman.cancelPut();
+  } else if (root["file"].asString() != fileman.getPutName()) {
+    retval.type = error;
+    retval.msg = "File upload request failed: Server reports filename " +
+                 root["file"].asString() + " but actual filename is " +
+                 fileman.getPutName();
+    fileman.cancelPut();
+  } else {
+    // sent successfully
+    if (!root["received"].asInt()) {
+      // everything sent
+      retval.type = notsend;
+      retval.msg = "succesfully uploaded file " + fileman.getPutName();
+      fileman.closePut();
+    } else {
+      retval.type = send;
+      retval.msg = "putdata";
+    }
+  }
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleGet(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    retval.msg = "File download request failed: Server reports: " +
+                 root["error"].asString();
+  } else if (root["file"].asString() != fileman.getGetName()) {
+    retval.type = error;
+    retval.msg = "File download request failed: Server reports filename " +
+                 root["file"].asString() + " but actual filename is " +
+                 fileman.getGetName();
+  } else {
+    fileman.setGetChunks(root["chunks"].asInt());
+    retval.type = send;
+    retval.msg = "getdata";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleGetdata(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  // the passed number of recieved chunks should equal the number of sent chunks
+  if (root["remaining"].asInt() != fileman.getGetRemainingChunks()) {
+    retval.type = error;
+    retval.msg = std::string("File download failed: Server reports number of "
+                             "remaining chunks as ") +
+                 std::to_string(root["remaining"].asInt()) +
+                 " but actual number is " +
+                 std::to_string(fileman.getGetRemainingChunks());
+    fileman.cancelGet();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    retval.msg =
+        "File download cancelled: Server reports: " + root["error"].asString();
+    fileman.cancelGet();
+  } else if (root["file"].asString() != fileman.getGetName()) {
+    retval.type = error;
+    retval.msg = "File download failed: Server reports filename " +
+                 root["file"].asString() + " but actual filename is " +
+                 fileman.getGetName();
+    fileman.cancelGet();
+  } else {
+    fileman.writeBase64(root["data"].asString());
+    // loaded successfully
+    if (fileman.getGetRemainingChunks() < 0) {
+      // everything sent
+      retval.type = notsend;
+      retval.msg = "succesfully downloaded file " + fileman.getGetName();
+      fileman.closeGet();
+    } else {
+      retval.type = send;
+      retval.msg = "getdata";
+    }
+  }
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleList(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    retval.msg = "File listing request failed: Server reports: " +
+                 root["error"].asString();
+  } else {
+    fileman.setListChunks(root["chunks"].asInt());
+    retval.type = send;
+    retval.msg = "listdata";
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleListdata(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+  vector<string> toadd;
+
+  // the passed number of recieved chunks should equal the number of sent chunks
+  if (root["remaining"].asInt() != fileman.getListRemainingChunks()) {
+    retval.type = error;
+    retval.msg = std::string("File listing failed: Server reports number of "
+                             "remaining chunks as ") +
+                 std::to_string(root["remaining"].asInt()) +
+                 " but actual number is " +
+                 std::to_string(fileman.getListRemainingChunks());
+    fileman.cancelList();
+  } else if (root["cancel"].asBool()) {
+    retval.type = error;
+    retval.msg =
+        "File listing cancelled: Server reports: " + root["error"].asString();
+    fileman.cancelList();
+  } else {
+    for (Json::Value i : root["names"])
+      toadd.push_back(i.asString());
+    fileman.putListData(toadd);
+    // loaded successfully
+    if (!fileman.getListRemainingChunks()) {
+      // everything sent
+      retval.type = notsend;
+      retval.msg = "Files on server:\n";
+      for (string s : fileman.getListData())
+        retval.msg += s + "\n";
+      fileman.closeList();
+    } else {
+      retval.type = send;
+      retval.msg = "listdata";
+    }
+  }
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleVersion(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    retval.msg = "Version check failed: Server reports " +
+                 root["version"].asString() + " but client is version " +
+                 protocolVersion;
+  } else {
+    retval.type = seton;
+    retval.msg = "Version check ok.";
+    doversion = false;
+  }
+
+  return retval;
+}
+
+CmdMan::CmdRet CmdMan::handleLogin(Json::Value root) {
+  CmdRet retval;
+  std::cerr << __PRETTY_FUNCTION__ << " begin" << std::endl;
+
+  if (!root["accept"].asBool()) {
+    retval.type = error;
+    retval.msg = "Login failed: " + root["error"].asString();
+  } else {
+    retval.type = seton;
+    retval.msg = "Login ok.";
+    dologin = false;
+  }
+
+  return retval;
+}

+ 0 - 51
cli/src/commands.cpp

@@ -1,51 +0,0 @@
-#include "../include/commands.hpp"
-#include <cstdio>
-
-CMD commands[]{
-    {CMD_HELP, "help", "show help"},
-    {CMD_STATUS, "status", "query status of IP"},
-    {CMD_DISCONNECT, "disconnect", "disconnect from IP"},
-    {CMD_PUT, "put", "upload file to IP and add to queue"},
-    {CMD_GET, "get", "retrieve file from deamon"},
-    {CMD_LIST, "list", "list files that are downloadable from deamon"}
-
-    /* TODO
-    {CMD_REMOVE	, "remove", "remove file from IP and queue (stops xfer
-    if required)"},
-    {CMD_SETUP	, "setup", "configure server at IP"},
-    {CMD_LOG	, "log", "show log from IP"}
-    */
-};
-
-COMMANDID getCmdIdFromString(const char *str) {
-  COMMANDID ret = CMD_UNKNOWN;
-  char temp[COMMANDLEN + 1] = {0};
-  int len = COMMANDLEN;
-  if (std::strlen(str) > COMMANDLEN) {
-    return ret;
-  } else
-    len = std::strlen(str);
-  for (int i = 0; i < len; i++)
-    temp[i] = std::tolower(str[i]);
-
-  for (int i = 0; i < sizeofarr(commands); i++) {
-    if (!std::strncmp(temp, commands[i].name, COMMANDLEN))
-      ret = commands[i].cmd;
-  }
-  return ret;
-}
-
-const char *getCmdStringFromId(COMMANDID id) {
-  const char *ret = NULL;
-  for (int i = 0; i < sizeofarr(commands); i++) {
-    if (commands[i].cmd == id)
-      ret = commands[i].name;
-  }
-  return ret;
-}
-
-void printCmds(void) {
-  for (CMD command : commands) {
-    printf("%-12s - %s\n", command.name, command.desc);
-  }
-}

+ 131 - 0
cli/src/fileman.cpp

@@ -0,0 +1,131 @@
+#include "../include/fileman.h"
+#include "../include/base64.h"
+
+FileMan::FileMan() { islisting = false; }
+
+FileMan::~FileMan() {
+  cancelGet();
+  cancelPut();
+  cancelList();
+}
+
+bool FileMan::isGetting() { return getfile.is_open(); }
+
+bool FileMan::isPutting() { return putfile.is_open(); }
+
+bool FileMan::isListing() { return islisting; }
+
+bool FileMan::openPut(const std::string &name) {
+  putname = name;
+  putfile.open(name, std::ios::ate | std::ios::binary);
+  if (putfile.is_open()) {
+    size_t size = putfile.tellg();
+    putsize = size;
+    putchunks = size / max_read_len + ((size % max_read_len) ? 1 : 0);
+    putchunksRemaining = putchunks;
+    putfile.seekg(std::ios::beg);
+    return true;
+  }
+  return false;
+}
+
+bool FileMan::openGet(const std::string &name) {
+  getname = name;
+  getchunks = 0;
+  getchunksRemaining = 0;
+  getfile.open(name, std::ios::app | std::ios::binary);
+  if (getfile.tellp() != std::ios::beg) {
+    closeGet();
+    return false;
+  }
+  return true;
+}
+
+bool FileMan::openList() {
+  if (isListing()) {
+    return false;
+  }
+  listdata = std::vector<std::string>();
+  islisting = true;
+  return true;
+}
+
+void FileMan::closePut() { putfile.close(); }
+
+void FileMan::closeGet() { getfile.close(); }
+
+void FileMan::closeList() { islisting = false; }
+
+void FileMan::cancelPut() {
+  if (isPutting()) {
+    closePut();
+  }
+}
+
+void FileMan::cancelGet() {
+  if (isGetting()) {
+    closeGet();
+    std::remove(getname.c_str());
+  }
+}
+
+void FileMan::cancelList() {
+  if (isListing()) {
+    closeList();
+  }
+}
+
+void FileMan::writeGet(const std::vector<char> data) {
+  getfile.write(data.data(), data.size());
+  getchunksRemaining--;
+}
+
+void FileMan::writeBase64(std::string data) {
+  writeGet(base64::decodeVector(data));
+}
+
+void FileMan::putListData(std::vector<std::string> names) {
+  listdata.insert(listdata.end(), names.begin(), names.end());
+  listchunksRemaining--;
+}
+
+std::vector<std::string> FileMan::getListData() { return listdata; }
+
+int FileMan::getGetChunks() { return getchunks; }
+
+int FileMan::getGetRemainingChunks() { return getchunksRemaining; }
+
+void FileMan::setGetChunks(int chunks) {
+  getchunks = chunks;
+  getchunksRemaining = chunks - 1;
+}
+
+void FileMan::setListChunks(int chunks) {
+  listchunks = chunks;
+  getchunksRemaining = chunks - 1;
+}
+
+std::vector<char> FileMan::readPut() {
+  char buf[max_read_len];
+  std::vector<char> data;
+  int read = putfile.readsome(buf, max_read_len);
+  data.assign(buf, buf + read);
+  putchunksRemaining--;
+  return data;
+}
+
+std::string FileMan::readBase64() { return base64::encodeVector(readPut()); }
+
+std::string FileMan::getPutName() { return putname; }
+
+std::string FileMan::getGetName() { return getname; }
+
+int FileMan::getPutChunks() { return putchunks; }
+
+int FileMan::getPutRemainingChunks() { return putchunksRemaining; }
+
+int FileMan::getPutSize() { return putsize; }
+
+int FileMan::getListRemainingChunks() { return listchunksRemaining; }
+
+int FileMan::getListChunks() { return listchunks; }

+ 572 - 0
cli/src/ioman.cpp

@@ -0,0 +1,572 @@
+#include "../include/ioman.h"
+#include "../include/global.h"
+
+#include <boost/asio.hpp>
+#include <iostream>
+
+#include <boost/algorithm/string.hpp>
+#include <string>
+#include <vector>
+
+#include <readline/history.h>
+#include <readline/readline.h>
+
+#include <poll.h>
+
+using std::string;
+using std::vector;
+
+using boost::asio::buffer;
+using boost::asio::ip::tcp;
+
+/* this will be provided by main.cpp for the readline callback */
+extern IoMan *gIOMAN;
+
+IoMan::IoMan(char *ipcstring) : cmdman(fileman) {
+  ipstring = std::string(ipcstring);
+  port = 1234;
+  tcpsock = new tcp::socket(ios);
+  connected = false;
+
+  /* to be put elsewhere */
+
+  /* setup json stuff */
+  Json::CharReaderBuilder rbuilder;
+  wbuilder.settings_["indentation"] = "";
+  reader = rbuilder.newCharReader();
+  runnetwork = false;
+  runinput = false;
+  runresponse = false;
+  versionstatus = off;
+  loginstatus = off;
+}
+
+IoMan::~IoMan() {
+  networkmutex.lock();
+  inputmutex.lock();
+  responsemutex.lock();
+  runnetwork = false;
+  runinput = false;
+  runresponse = false;
+  networkmutex.unlock();
+  inputmutex.unlock();
+  responsemutex.unlock();
+
+  initcv.notify_all();
+  localcv.notify_all();
+  netcv.notify_all();
+
+  tnetwork.join();
+  tinput.join();
+  tresponse.join();
+
+  if (connected) {
+    disconnect();
+  }
+
+  delete tcpsock;
+  delete reader;
+}
+
+void IoMan::printMessage(string nouse, OutMsgType nouse2) {}
+
+bool IoMan::connect() {
+  tcp::endpoint *ep;
+
+  ep = new tcp::endpoint(boost::asio::ip::address::from_string(ipstring), 1234);
+
+  // establish connection
+  printMessage("IoMan::connect() connecting to " + ipstring, debug);
+  tcpsock->connect(*ep, errcode);
+  if (errcode) {
+    delete ep;
+    printMessage("IoMan::connect() couldnt connect to " + ipstring + "\n" +
+                     errcode.message(),
+                 error);
+    return false;
+  }
+  connected = true;
+  delete ep;
+  return true;
+}
+
+void IoMan::disconnect() {
+  printMessage("IoMan::disconnect()", debug);
+  tcpsock->close();
+  connected = false;
+}
+
+bool IoMan::init() {
+  CmdMan::CmdRet ret;
+  string work;
+  Json::Value root;
+  std::unique_lock<std::mutex> ulock;
+
+  printMessage("IoMan::Init() begin", debug);
+
+  if (!connect())
+    return false;
+
+  printMessage("IoMan::Init() versioncheck", debug);
+
+  localmutex.lock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+  localinput.push_back("version " + protocolVersion);
+  localmutex.unlock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+               debug);
+  localcv.notify_all();
+
+  printMessage("IoMan::Init() begin", debug);
+
+  runnetwork = true;
+  runinput = true;
+  runresponse = true;
+
+  tnetwork = std::thread(&IoMan::networkMain, this);
+  tinput = std::thread(&IoMan::inputMain, this);
+  tresponse = std::thread(&IoMan::responseMain, this);
+
+  ulock = std::unique_lock<std::mutex>(initmutex);
+  while (versionstatus == off) {
+    initcv.wait(ulock);
+  }
+  if (versionstatus == err) {
+    runnetwork = false;
+    runinput = false;
+    runresponse = false;
+    return false;
+  }
+  initmutex.unlock();
+  initcv.notify_all();
+
+  printWelcomeMessage();
+  return true;
+}
+
+/* loop to fetch data from the network, doing light preprocessing on it to be
+ * handled by responseMain */
+void IoMan::networkMain() {
+  vector<Json::Value> toput;
+  char *recvjson;
+  Json::Value root;
+  unsigned int jsonsize, readsize;
+
+  printMessage("IoMan::networkMain() begin", debug);
+  networkmutex.lock();
+  while (runnetwork) {
+    networkmutex.unlock();
+    /*
+    read from network until \n
+    try to parse json
+    - output error if not ok
+    store all ok json in local vector
+
+    get networkmutex
+    put all local jsons into network vector
+    release networkmutex
+    */
+
+    // read from network
+    readsize = boost::asio::read_until(*tcpsock, recvbuf, '\n', errcode);
+    printMessage(string(__PRETTY_FUNCTION__) + string(" asio::read() ok ") +
+                     std::to_string(readsize),
+                 debug);
+    // printMessage(string("have ") + std::to_string(toprocess.size()) +
+    // string(" commands"), debug);
+    if (errcode && errcode != boost::asio::error::eof) {
+      printMessage("IoMan::networkMain() couldnt read json data\n" +
+                       errcode.message(),
+                   error);
+      continue;
+    }
+    if (!readsize)
+      break;
+    recvjson = (char *)(boost::asio::buffer_cast<const char *>(recvbuf.data()));
+    recvjson[readsize] = 0;
+    while (strchr(recvjson, '\n')) {
+      // parse
+      jsonsize = strchr(recvjson, '\n') - recvjson + 1;
+
+      printMessage(string(__PRETTY_FUNCTION__) + string(" found jsondata ") +
+                       string(recvjson),
+                   debug);
+
+      if (!reader->parse(recvjson, recvjson + jsonsize, &root, &jsonerror)) {
+        printMessage("IoMan::networkMain() couldnt parse json data: " +
+                         jsonerror,
+                     error);
+        recvbuf.consume(jsonsize);
+        continue;
+      }
+      recvbuf.consume(jsonsize);
+      readsize -= jsonsize;
+
+      printMessage(
+          string(__PRETTY_FUNCTION__) + string(" remaining recvbuf ") +
+              string(boost::asio::buffer_cast<const char *>(recvbuf.data())),
+          debug);
+
+      for (int i = 0; i < jsonsize; i++)
+        recvjson++;
+      // store locally
+      toput.push_back(root);
+    }
+
+    if (toput.size()) {
+      // put into global vector
+      netmutex.lock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"),
+                   debug);
+      netinput.insert(netinput.end(), toput.begin(), toput.end());
+      netmutex.unlock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"),
+                   debug);
+    }
+    netcv.notify_all();
+
+    // clean up local stuff
+    toput = vector<Json::Value>();
+    recvbuf.consume(readsize);
+    networkmutex.lock();
+  }
+}
+
+/* loop to handle input from the user and responseMain, sending data via network
+ * if required */
+void IoMan::inputMain() {
+  vector<string> toprocess;
+  string command;
+  vector<string> args;
+  CmdMan::CmdRet cmdret;
+  std::unique_lock<std::mutex> ulock;
+
+  size_t prev, index, quot;
+
+  printMessage("IoMan::inputMain() begin", debug);
+  inputmutex.lock();
+  while (runinput) {
+    inputmutex.unlock();
+    /*
+    get inputmutex
+    read all input vector into local vector
+    release inputmutex
+
+    process inputs
+    send to server if required
+    */
+
+    // read into local vector
+    ulock = std::unique_lock<std::mutex>(localmutex);
+    while (!localinput.size() && runinput) {
+      localcv.wait(ulock);
+    }
+    printMessage(string(__PRETTY_FUNCTION__) + string(" has localmutex"),
+                 debug);
+    toprocess = vector<string>(localinput);
+    localinput = vector<string>();
+    localmutex.unlock();
+    printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+                 debug);
+    localcv.notify_all();
+
+    if (!runinput)
+      return;
+
+    // printMessage(string("have ") + std::to_string(toprocess.size()) +
+    // string(" commands"), debug);
+
+    // process
+    for (string cmd : toprocess) {
+      args = vector<string>();
+
+      /* tokenize string into command and arguments vector*/
+      if ((index = cmd.find(" ")) == string::npos) {
+        // only command no args
+        command = cmd;
+      } else {
+        command = cmd.substr(0, index);
+        index++;
+
+        bool end_tokenizing = false;
+        while (!end_tokenizing) {
+          // find first char thats not a space
+          while (cmd[index] == ' ') {
+            index++;
+
+            // bounds check
+            if (index == cmd.size())
+              end_tokenizing = true;
+          }
+          if (end_tokenizing)
+            break;
+
+          cmd = cmd.substr(index);
+
+          if (cmd[0] == '\"') {
+            // quoted string
+            cmd = cmd.substr(1);
+            index = cmd.find("\"");
+            args.push_back(cmd.substr(0, index));
+            index++;
+
+            /*
+            tokens.push_back(cmd.substr(0, ++index));
+            */
+
+            // char after closing quote should be space while within bounds
+            if (index == cmd.size())
+              end_tokenizing = true;
+          } else {
+            // non-quoted string
+            index = cmd.find(" ");
+            if (index == string::npos) { // no spaces, last arg
+              args.push_back(cmd);
+              end_tokenizing = true;
+            } else {
+              args.push_back(cmd.substr(0, index));
+            }
+          }
+        }
+      }
+
+      cmdret = cmdman.execute(command, args);
+
+      // determine wether to send something and do so if required
+      switch (cmdret.type) {
+      case CmdMan::rettype::send: {
+        printMessage("IoMan::inputMain() sending json \"" + cmdret.msg + "\"",
+                     debug);
+        boost::asio::write(*tcpsock, buffer(cmdret.msg + "\n"), errcode);
+        if (errcode) {
+          printMessage("IoMan::inputMain() couldnt send json data\n" +
+                           errcode.message() + "\n",
+                       error);
+          continue;
+        }
+        break;
+      }
+      case CmdMan::rettype::notsend: {
+        printMessage(cmdret.msg, normal);
+        break;
+      }
+      case CmdMan::rettype::error: {
+        printMessage(cmdret.msg, error);
+        break;
+      }
+      default: {
+        printMessage(string(__PRETTY_FUNCTION__) + string(" unknown rettype"),
+                     debug);
+      }
+      }
+    }
+
+    // clean up local stuff
+    toprocess = vector<string>();
+    inputmutex.lock();
+  }
+}
+
+/* loop to handle responses that have been fetched by netMain and possibly add
+ * new commands to be handled by inputMain */
+void IoMan::responseMain() {
+  vector<Json::Value> toprocess;
+  vector<string> toput;
+  CmdMan::CmdRet cmdret;
+  std::unique_lock<std::mutex> ulock;
+
+  printMessage("IoMan::responseMain() begin", debug);
+  responsemutex.lock();
+  while (runresponse) {
+    responsemutex.unlock();
+    /*
+    get networkmutex
+    read all network vector into local vector
+    release networkmutex
+
+    process all jsons
+    process putdata
+    process getdata
+    process listdata
+
+    get inputmutex
+    place new commands into input vector
+    release inputmutex
+    */
+
+    // read into local vector
+    ulock = std::unique_lock<std::mutex>(netmutex);
+    while (!netinput.size() && runresponse) {
+      netcv.wait(ulock);
+    }
+    printMessage(string(__PRETTY_FUNCTION__) + string(" get netmutex"), debug);
+    toprocess = vector<Json::Value>(netinput);
+    netinput = vector<Json::Value>();
+    netmutex.unlock();
+    printMessage(string(__PRETTY_FUNCTION__) + string(" release netmutex"),
+                 debug);
+    netcv.notify_all();
+
+    if (!runresponse)
+      return;
+
+    // process jsons
+    for (Json::Value root : toprocess) {
+      cmdret = cmdman.handle(root);
+      switch (cmdret.type) {
+      case CmdMan::rettype::close: {
+        /* TODO i dunno */
+        mainmutex.lock();
+        runmain = false;
+        mainmutex.unlock();
+        break;
+      }
+      case CmdMan::rettype::error: {
+        initmutex.lock();
+        if (versionstatus == off)
+          versionstatus = err;
+        else if (loginstatus == off)
+          loginstatus = err;
+        initmutex.unlock();
+        initcv.notify_all();
+        printMessage(cmdret.msg, error);
+        break;
+      }
+      case CmdMan::rettype::seton: {
+        initmutex.lock();
+        if (versionstatus == off)
+          versionstatus = on;
+        else
+          loginstatus = on;
+        initmutex.unlock();
+        initcv.notify_all();
+      }
+      case CmdMan::rettype::notsend: {
+        printMessage(cmdret.msg, normal);
+        break;
+      }
+      case CmdMan::rettype::send: {
+        toput.push_back(cmdret.msg);
+        break;
+      }
+      }
+    }
+
+    if (toput.size()) {
+      // put new commands into global vector
+      localmutex.lock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"),
+                   debug);
+      localinput.insert(localinput.end(), toput.begin(), toput.end());
+      localmutex.unlock();
+      printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+                   debug);
+    }
+    localcv.notify_all();
+
+    // clean up local stuff
+    toprocess = vector<Json::Value>();
+    toput = vector<string>();
+    responsemutex.lock();
+  }
+}
+
+/* this is the handler that readlines alternative interface will use to process
+ * user input */
+void ioman_readlineHandler(char *line) {
+  vector<string> tokens;
+  if (!line) {
+    printf("\nNULLBURGER\n");
+    gIOMAN->mainmutex.lock();
+    gIOMAN->runmain = false;
+    gIOMAN->mainmutex.unlock();
+  } else {
+    // split input line into tokens
+    boost::algorithm::split(tokens, std::string(line),
+                            boost::algorithm::is_any_of(" "),
+                            boost::algorithm::token_compress_on);
+    if (strlen(line) && tokens.size()) {
+      add_history(line);
+
+      gIOMAN->localmutex.lock();
+      gIOMAN->printMessage(string(__PRETTY_FUNCTION__) +
+                               string(" get localmutex"),
+                           gIOMAN->debug);
+      gIOMAN->localinput.push_back(line);
+      gIOMAN->localmutex.unlock();
+      gIOMAN->printMessage(string(__PRETTY_FUNCTION__) +
+                               string(" release localmutex"),
+                           gIOMAN->debug);
+      gIOMAN->localcv.notify_all();
+    }
+    free(line);
+  }
+}
+
+/* main user input loop */
+void IoMan::run() {
+  printMessage("IoMan::run() begin", debug);
+  //~ char *line = NULL, *user = NULL, *pass = NULL;
+  char *user = NULL, *pass = NULL;
+  vector<string> tokens;
+  string work, command;
+  CmdMan::CmdRet cmdret;
+  Json::Value root;
+  std::unique_lock<std::mutex> ulock;
+  struct pollfd inPipeStatus;
+  inPipeStatus.fd = STDIN_FILENO;
+  inPipeStatus.events = POLLIN;
+
+  runmain = true;
+
+  while (!user) {
+    user = readline(getUserPrompt().c_str());
+    printMessage("Using user: " + string(user), error);
+  }
+  while (!pass) {
+    pass = readline(getPassPrompt().c_str());
+    printMessage("Using pass: " + string(pass), error);
+  }
+
+  printMessage("IoMan::run() login", debug);
+
+  localmutex.lock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" get localmutex"), debug);
+  localinput.push_back("login " + string(user) + " " + string(pass));
+  localmutex.unlock();
+  printMessage(string(__PRETTY_FUNCTION__) + string(" release localmutex"),
+               debug);
+  free(user);
+  free(pass);
+  localcv.notify_all();
+
+  ulock = std::unique_lock<std::mutex>(initmutex);
+  while (loginstatus == off) {
+    initcv.wait(ulock);
+  }
+  if (loginstatus == err)
+    return;
+  initmutex.unlock();
+  initcv.notify_all();
+
+  rl_callback_handler_install(getCmdPrompt().c_str(),
+                              (rl_vcpfunc_t *)&ioman_readlineHandler);
+
+  mainmutex.lock();
+  while (runmain) {
+    mainmutex.unlock();
+
+    poll(&inPipeStatus, 1, 100);
+
+    if (inPipeStatus.revents & POLLIN) {
+      rl_callback_read_char();
+    }
+    if (!connected)
+      break;
+    mainmutex.lock();
+  }
+  mainmutex.unlock();
+
+  // Remove the handler
+  rl_callback_handler_remove();
+}

+ 0 - 157
cli/src/iomanager.cpp

@@ -1,157 +0,0 @@
-#include "../include/iomanager.hpp"
-
-#include <iostream>
-
-using boost::asio::buffer;
-
-IoManager::IoManager(char *ipcstring) {
-  ipstring = std::string(ipcstring);
-  port = 1234;
-  tcpsock = new tcp::socket(ios);
-  wbuilder.settings_["indentation"] = "";
-  // const std::string output = Json::writeString(wbuilder, root);
-  reader = rbuilder.newCharReader();
-}
-
-bool IoManager::sendJson(Json::Value root) {
-  boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, root)),
-                     errcode);
-  if (errcode) {
-    std::cerr << "couldnt send json data" << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-  return true;
-}
-
-bool IoManager::receiveJson(boost::asio::streambuf &recvbuf) {
-  // use transfer_at_least(1) to avoid deadlock with transfer_all()
-  boost::asio::read(*tcpsock, recvbuf, boost::asio::transfer_at_least(1),
-                    errcode);
-  if (errcode && errcode != boost::asio::error::eof) {
-    std::cerr << "couldnt recieve json data" << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-  return true;
-}
-
-bool IoManager::parseJson(Json::Value *root, boost::asio::streambuf &recvbuf) {
-  const char *recvjson;
-  std::string jsonerror;
-  recvjson = boost::asio::buffer_cast<const char *>(recvbuf.data());
-  if (!reader->parse(recvjson, recvjson + recvbuf.size(), root, &jsonerror)) {
-    std::cerr << "couldnt parse json data" << std::endl
-              << jsonerror << std::endl;
-    return false;
-  }
-  recvbuf.consume(recvbuf.size());
-  return true;
-}
-
-bool IoManager::connect() {
-  boost::asio::streambuf recvbuf;
-  tcp::endpoint *ep;
-  Json::Value root, checkok;
-  bool bcheckok;
-
-  ep = new tcp::endpoint(boost::asio::ip::address::from_string(ipstring), 1234);
-
-  // establish connection
-  std::cerr << "connecting to " << ipstring << std::endl;
-  tcpsock->connect(*ep, errcode);
-  if (errcode) {
-    std::cerr << "couldnt connect to " << ipstring << std::endl
-              << errcode.message() << std::endl;
-    return false;
-  }
-
-  // send version check
-  root["version"] = VERSION;
-
-  std::cerr << root << std::endl;
-
-  if (!sendJson(root))
-    return false;
-  // receive answer
-  if (!receiveJson(recvbuf))
-    return false;
-  if (!parseJson(&root, recvbuf))
-    return false;
-
-  // dump for GUI
-  std::cout << root << std::endl;
-
-  // check if version check was ok
-  checkok = root["accept"];
-
-  if (!checkok.asBool()) {
-    std::cerr << "version check failed. client version is " << VERSION
-              << std::endl
-              << "server reports version " << root["version"] << std::endl;
-    return false;
-  }
-
-  printf("check ok\n\n\n");
-  fflush(stdout);
-
-  /* */
-  // TODO remove hardcoded login
-  root = Json::Value();
-  // send version check
-  // TODO make client version global
-  root["user"] = "user";
-  root["pass"] = "pass";
-
-  std::cerr << root << std::endl;
-
-  // send login
-  if (!sendJson(root))
-    return false;
-  // receive answer to login
-  if (!receiveJson(recvbuf))
-    return false;
-  if (!parseJson(&root, recvbuf))
-    return false;
-
-  // dump for GUI
-  std::cout << root << std::endl;
-  /* */
-
-  // clean up
-  delete ep;
-
-  return true;
-}
-
-IoManager::~IoManager() {
-
-  /* */
-
-  boost::asio::streambuf recvbuf;
-  Json::Value root, checkok;
-  const char *recvjson;
-  std::string jsonerror;
-
-  // TODO remove hardcoded login
-  root = Json::Value();
-  // send version check
-  // TODO make client version global
-  root["command"] = "close";
-
-  std::cerr << root << std::endl;
-
-  // send disconnect
-  sendJson(root);
-  // recieve answer to disconnect
-  receiveJson(recvbuf);
-  parseJson(&root, recvbuf);
-
-  // dump to GUI
-  std::cout << root << std::endl;
-  /* */
-
-  tcpsock->close();
-  delete tcpsock;
-  delete reader;
-}

+ 37 - 0
cli/src/machineioman.cpp

@@ -0,0 +1,37 @@
+#include "../include/machineioman.h"
+
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <iostream>
+#include <limits>
+#include <vector>
+
+#include <readline/readline.h>
+
+using boost::asio::buffer;
+
+void MachineIoMan::printMessage(std::string msg, OutMsgType type) {
+  switch (type) {
+  case normal: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case error: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case debug: {
+    std::cerr << msg << std::endl;
+    break;
+  }
+  }
+}
+
+void MachineIoMan::printWelcomeMessage() {
+  std::cout << "please enter user and password" << std::endl;
+}
+
+std::string MachineIoMan::getCmdPrompt() { return ""; }
+
+std::string MachineIoMan::getUserPrompt() { return ""; }
+std::string MachineIoMan::getPassPrompt() { return ""; }

+ 0 - 274
cli/src/machineiomanager.cpp

@@ -1,274 +0,0 @@
-#include "../include/machineiomanager.hpp"
-#include "../include/base64.hpp"
-#include "../include/commands.hpp"
-
-#include <boost/algorithm/string.hpp>
-#include <fstream>
-#include <iostream>
-#include <limits>
-#include <vector>
-
-#include <readline/readline.h>
-
-using boost::asio::buffer;
-
-void MachineIoManager::run() {
-  std::cerr << "MachineIoManager::run() says hello!" << std::endl;
-
-  boost::asio::streambuf recvbuf;
-
-  Json::Value root, recv;
-  const char *recvjson;
-
-  bool keep_reading = true;
-  COMMANDID cmd;
-  char *line = NULL;
-
-  std::vector<std::string> tokens, pathtemp;
-
-  std::fstream fs;
-
-  // .open(path, mode; mode std::fstream::in, out, binary, trunc(ate))
-  // .read(char* dest, int size)
-  // .write(char* src, int size)
-  // .eof()
-  // .seekg(offset, ios_base::beg, cur, end)
-  // .tellg()
-  // .close()
-
-  while (keep_reading) {
-    // clean up
-    free(line);
-    root = Json::Value();
-    recv = Json::Value();
-    recvbuf.consume(recvbuf.size());
-
-    // read an input line
-    line = readline("");
-
-    // if no input continue
-    if (strlen(line) == 0) {
-      continue;
-    }
-
-    boost::algorithm::split(tokens, std::string(line),
-                            boost::algorithm::is_any_of(" "),
-                            boost::algorithm::token_compress_on);
-
-    std::cerr << "tokens are: [ ";
-    for (int i = 0; i < tokens.size(); i++) {
-      std::cerr << tokens[i] << " ";
-    }
-    std::cerr << "]" << std::endl;
-
-    if (tokens.size() < 1) {
-      continue;
-    }
-
-    cmd = getCmdIdFromString(tokens[0].c_str());
-
-    switch (cmd) {
-    case CMD_DISCONNECT: {
-      std::cerr << "disconnecting\n";
-      keep_reading = false;
-      break;
-    }
-    case CMD_STATUS: {
-      root["command"] = "status";
-
-      // send request
-      sendJson(root);
-      // receive answer
-      receiveJson(recvbuf);
-      //
-      parseJson(&recv, recvbuf);
-
-      // dump received data to stdout for gui to handle
-      std::cout << recv;
-
-      break;
-    }
-    case CMD_PUT: {
-      int size, chunks, haveread;
-      char rbuf[BLOCKSIZE];
-      if (tokens.size() < 2) {
-        std::cerr << "no files specified" << std::endl;
-        break;
-      }
-      fs.open(tokens[1], std::fstream::in | std::fstream::binary);
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-      fs.seekg(0, fs.end);
-      size = fs.tellg();
-      fs.seekg(0, fs.beg);
-      chunks = size / BLOCKSIZE + ((size % BLOCKSIZE) ? 1 : 0);
-      std::cerr << "size is " << size << " chunks are " << chunks << std::endl;
-
-      if (!size) {
-        std::cerr << "not sending empty file" << std::endl;
-        break;
-      }
-
-      boost::algorithm::split(pathtemp, tokens[1],
-                              boost::algorithm::is_any_of("/"),
-                              boost::algorithm::token_compress_on);
-
-      root["command"] = "put";
-      root["file"] = pathtemp.back();
-      root["size"] = size;
-
-      std::cerr << root;
-
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-      // dump to stdout for GUI
-      std::cout << recv;
-
-      // check correct command once asychronous
-      if (recv["accept"].asBool()) {
-        // request ok, start sending...
-        root = Json::Value();
-        root["command"] = "put";
-        root["cancel"] = false;
-
-        std::cerr << root;
-
-        while (chunks) {
-          haveread = fs.readsome(rbuf, BLOCKSIZE);
-          chunks--;
-          root["data"] = base64::encodeVector(
-              std::vector<char>(rbuf, rbuf + haveread * sizeof(char)));
-          root["remaining"] = chunks;
-          if (!sendJson(root) | !receiveJson(recvbuf) |
-              !parseJson(&recv, recvbuf)) {
-            // error occured, stop working
-            break;
-          }
-          if (recv["cancel"].asBool()) {
-            // transfer cancelled by server
-            break;
-          }
-          std::cout << root;
-        }
-        if (chunks)
-          std::cerr << "not all data sent, remain chunks " << chunks
-                    << std::endl;
-      }
-      break;
-    }
-    case CMD_GET: {
-      int chunks, rec = 0;
-      std::vector<char> wtmp;
-      char wbuf[BLOCKSIZE];
-      if (tokens.size() < 2) {
-        std::cerr << "no files specified" << std::endl;
-        break;
-      }
-      fs.open(tokens[1],
-              std::fstream::out | std::fstream::binary | std::fstream::trunc);
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-
-      root["command"] = "get";
-      root["file"] = tokens[1];
-
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-
-      // dump received data for GUI
-      std::cout << recv << std::endl;
-
-      if (recv["accept"].asBool()) {
-        // for ACK
-        root["command"] = "get";
-        root["cancel"] = false;
-        // request ok, start receiving
-        chunks = std::numeric_limits<int>::max();
-        std::cerr << "survive" << std::endl;
-        while (chunks) {
-          std::cerr << "survive" << std::endl;
-          if (!receiveJson(recvbuf) | !parseJson(&recv, recvbuf))
-            break;
-          std::cerr << "survive" << std::endl;
-          // dump received data for GUI
-          std::cout << recv << std::endl;
-          wtmp = base64::decodeVector(recv["data"].asString());
-          fs.write(wtmp.data(), wtmp.size());
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      }
-      if (recv["cancel"].asBool())
-        std::cerr << "transfer was cancelled" << std::endl;
-      if (recv["remaining"].asInt())
-        std::cerr << "not all data received, remain chunks "
-                  << recv["remaining"].asInt();
-      break;
-    }
-    case CMD_LIST: {
-      int chunks, rec = 0;
-      root["command"] = "list";
-      if (!sendJson(root) | !receiveJson(recvbuf) |
-          !parseJson(&recv, recvbuf)) {
-        // error occured, stop working
-        break;
-      }
-      // dump received data for GUI
-      std::cout << recv << std::endl;
-
-      if (recv["accept"].asBool()) {
-        // ACK?
-        root["command"] = "list";
-        root["cancel"] = false;
-        chunks = std::numeric_limits<int>::max();
-        while (chunks) {
-          if (!receiveJson(recvbuf) | !parseJson(&recv, recvbuf))
-            break;
-          // dump received data for GUI
-          std::cout << recv << std::endl;
-          /*
-          Json::Value arraything = recv["names"];
-          if(arraything.isArray())
-          for(Json::Value d : recv["names"])
-          */
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      }
-      break;
-    }
-    default: {
-      std::cerr << "unknown command " << line << "\n";
-      break;
-    }
-    }
-    std::printf("\n");
-
-    // if a command used fs, close it now
-    if (fs.is_open())
-      fs.close();
-  }
-  free(line);
-
-  return;
-}

+ 11 - 10
cli/src/main.cpp

@@ -1,14 +1,15 @@
-#include "../include/commands.hpp"
-#include "../include/machineiomanager.hpp"
-#include "../include/useriomanager.hpp"
+#include "../include/machineioman.h"
+#include "../include/userioman.h"
 
-#include <boost/asio.hpp>
 #include <boost/program_options.hpp>
 #include <iostream>
 #include <string>
 
 namespace bpo = boost::program_options;
 
+/* this is provided for the readline callback in IoMan */
+IoMan *gIOMAN = NULL;
+
 void show_help(char *exec) {
   std::printf("ccats command line interface\n");
   std::printf("usage: %s IP [Options]\n", exec);
@@ -26,7 +27,7 @@ int main(int argc, char **argv) {
   bpo::variables_map vm;
   unsigned int machine = 0;
   const char *file = NULL;
-  IoManager *ioman;
+  IoMan *ioman;
 
   if (argc < 2 || !std::strncmp(argv[1], "--help", 6)) {
     show_help(argv[0]);
@@ -65,15 +66,15 @@ int main(int argc, char **argv) {
               file ? file : "");
 
   if (machine) {
-    ioman = new MachineIoManager(argv[1]);
+    ioman = new MachineIoMan(argv[1]);
   } else {
-    ioman = new UserIoManager(argv[1]);
+    ioman = new UserIoMan(argv[1]);
   }
-
-  // ‘MachineIoManager::MachineIoManager(char*&)’
-  if (ioman->connect()) {
+  if (ioman->init()) {
+    gIOMAN = ioman;
     ioman->run();
   }
   delete ioman;
+  gIOMAN = NULL;
   std::printf("done\n");
 }

+ 40 - 0
cli/src/userioman.cpp

@@ -0,0 +1,40 @@
+#include "../include/userioman.h"
+
+#include <boost/algorithm/string.hpp>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#include <readline/history.h>
+#include <readline/readline.h>
+
+using boost::asio::buffer;
+
+void UserIoMan::printMessage(std::string msg, OutMsgType type) {
+  msgmutex.lock();
+  switch (type) {
+  case normal: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case error: {
+    std::cout << msg << std::endl;
+    break;
+  }
+  case debug: {
+    std::cerr << msg << std::endl;
+    break;
+  }
+  }
+  rl_redisplay();
+  msgmutex.unlock();
+}
+
+void UserIoMan::printWelcomeMessage() {
+  std::cout << "please enter user and password" << std::endl;
+}
+
+std::string UserIoMan::getCmdPrompt() { return "ccats> "; }
+
+std::string UserIoMan::getUserPrompt() { return "User: "; }
+std::string UserIoMan::getPassPrompt() { return "Pass: "; }

+ 0 - 401
cli/src/useriomanager.cpp

@@ -1,401 +0,0 @@
-#include "../include/useriomanager.hpp"
-#include "../include/base64.hpp"
-#include "../include/commands.hpp"
-
-#include <boost/algorithm/string.hpp>
-#include <fstream>
-#include <iostream>
-#include <vector>
-
-#include <readline/history.h>
-#include <readline/readline.h>
-
-#define BLOCKSIZE 8
-
-using boost::asio::buffer;
-
-void UserIoManager::run() {
-  std::cout << "UserIoManager::run() says hello!" << std::endl;
-
-  bool keep_reading = true;
-  COMMANDID cmd;
-
-  char *line = NULL;
-  std::vector<std::string> tokens;
-
-  while (keep_reading) {
-    free(line);
-
-    // read an input line
-    line = readline("ccats> ");
-
-    // if no input, do not add to history, and go to reading next line
-    if (strlen(line) == 0) {
-      continue;
-    }
-
-    // split input line into tokens
-    boost::algorithm::split(tokens, std::string(line),
-                            boost::algorithm::is_any_of(" "),
-                            boost::algorithm::token_compress_on);
-
-    // if input contains only spaces, do not add to history, and go to reading
-    // next line
-    if (tokens.size() < 1) {
-      continue;
-    }
-
-    // add the line to history
-    add_history(line);
-
-    // check for passed command
-    cmd = getCmdIdFromString(tokens[0].c_str());
-
-    switch (cmd) {
-    case CMD_STATUS: {
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, checkok;
-      const char *recvjson;
-
-      // ask for status
-      root["command"] = "status";
-      boost::asio::write(*tcpsock, buffer(Json::writeString(wbuilder, root)),
-                         errcode);
-      if (errcode) {
-        std::cerr << "couldnt send status query to server " << ipstring
-                  << std::endl
-                  << errcode.message() << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // recieve answer to status query
-      boost::asio::read(*tcpsock, recvbuf, boost::asio::transfer_at_least(1),
-                        errcode);
-      if (errcode && errcode != boost::asio::error::eof) {
-        std::cerr << "couldnt recieve status from " << ipstring << std::endl
-                  << errcode.message() << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // parse json
-      recvjson = boost::asio::buffer_cast<const char *>(recvbuf.data());
-      if (!reader->parse(recvjson, recvjson + recvbuf.size(), &root,
-                         &jsonerror)) {
-        std::cerr << "couldnt parse recieved json" << std::endl
-                  << jsonerror << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      // remove processed data from recvbuf
-      recvbuf.consume(recvbuf.size());
-
-      // check if received answer to right query
-      checkok = root["command"];
-
-      if (checkok.asString().compare("status") != 0) {
-        std::cerr << "command check failed. client sent command \"status\""
-                  << std::endl
-                  << "server replied to command \"" << checkok << "\""
-                  << std::endl;
-        keep_reading = false;
-        continue;
-      } else {
-        std::cout << "server replied with status: " << root["response"]
-                  << std::endl;
-      }
-
-      break;
-    }
-    case CMD_PUT: {
-
-      if (tokens.size() < 2) {
-        std::cerr << "no file specified" << std::endl;
-        continue;
-      }
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, ackRecv;
-
-      std::fstream fs;
-      fs.open(tokens[1].c_str(), std::fstream::in | std::fstream::binary);
-
-      if (!fs.is_open()) {
-        std::cerr << "couldnt open file " << tokens[1] << std::endl;
-        break;
-      }
-      // determine file size
-      fs.seekg(0, fs.end);
-      int filelen = fs.tellg();
-      fs.seekg(0, fs.beg);
-
-      // determine file name
-      std::vector<std::string> path;
-      boost::algorithm::split(path, tokens[1], boost::algorithm::is_any_of("/"),
-                              boost::algorithm::token_compress_on);
-      std::string filename = path.back();
-
-      // send first query to server
-      root["command"] = "put";
-      root["file"] = filename;
-      root["size"] = filelen;
-
-      std::cout << root << std::endl;
-
-      if (!sendJson(root)) {
-        keep_reading = false;
-        continue;
-      }
-      std::cout << "sent request to server " << ipstring << std::endl;
-
-      if (!receiveJson(recvbuf)) {
-        keep_reading = false;
-        continue;
-      }
-      if (!parseJson(&root, recvbuf)) {
-        std::cerr << "received invalid JSON from server" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << root << std::endl;
-
-      if (!root["accept"].asBool()) {
-        std::cerr << "server did not accept request" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << "request successful, beginning to send file to " << ipstring
-                << std::endl;
-
-      // determine number of chunks
-      int size_remainder = filelen % BLOCKSIZE;
-      int chunks = filelen / BLOCKSIZE + ((size_remainder > 0) ? 1 : 0);
-
-      char readbuf[BLOCKSIZE];
-
-      // send file
-      root = Json::Value();
-      root["command"] = "put";
-      root["cancel"] = false;
-
-      while (chunks > 0) {
-        chunks--;
-        if (!chunks &
-            (size_remainder !=
-             0)) { // size_remainder is the size of the last chunk if it's not 0
-          fs.read(readbuf, size_remainder);
-          root["data"] = base64::encodeVector(std::vector<char>(
-              readbuf, readbuf + size_remainder * sizeof(char)));
-        } else { // not last chunk or last chunk has full length
-          fs.read(readbuf, BLOCKSIZE);
-          root["data"] = base64::encodeVector(
-              std::vector<char>(readbuf, readbuf + BLOCKSIZE * sizeof(char)));
-        }
-        root["remaining"] = chunks;
-
-        std::cout << root << std::endl;
-
-        if (!sendJson(root)) {
-          std::cerr << "couldn't send file, " << chunks + 1
-                    << " chunks not sent" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-
-        if (!receiveJson(recvbuf)) {
-          continue;
-        }
-        if (!parseJson(&ackRecv, recvbuf)) {
-          std::cerr << "received invalid JSON from server" << std::endl;
-          continue;
-        }
-        // TODO: check for cancel answer
-      }
-
-      fs.close();
-      std::cout << "successfully sent file to " << ipstring << std::endl;
-
-      break;
-    }
-    case CMD_GET: {
-
-      if (tokens.size() < 2) {
-        std::cerr << "no file specified" << std::endl;
-        continue;
-      }
-
-      boost::asio::streambuf recvbuf;
-      Json::Value root, recvAck;
-
-      // send first query to server
-      root["command"] = "get";
-      root["file"] = tokens[1];
-
-      std::cout << root << std::endl;
-
-      if (!sendJson(root)) {
-        keep_reading = false;
-        continue;
-      }
-      std::cout << "sent request to server " << ipstring << std::endl;
-
-      if (!receiveJson(recvbuf)) {
-        keep_reading = false;
-        continue;
-      }
-      if (!parseJson(&root, recvbuf)) {
-        std::cerr << "received invalid JSON from server" << std::endl;
-        continue;
-      }
-
-      std::cout << root << std::endl;
-
-      if (!root["accept"].asBool()) {
-        std::cerr << "server did not accept request" << std::endl;
-        keep_reading = false;
-        continue;
-      }
-
-      std::cout << "request successful, beginning to receive file from "
-                << ipstring << std::endl;
-
-      // open file stream
-      std::fstream fs;
-      fs.open(tokens[1].c_str(),
-              std::fstream::out | std::fstream::trunc | std::fstream::binary);
-      std::vector<char> writebuf;
-
-      // number of remaining chunks, dummy value to entry loop
-      int remaining = std::numeric_limits<int>::max();
-      int recvCount = 0;
-
-      std::cout << "foobar" << std::endl;
-
-      while (remaining > 0) {
-
-        std::cout << "foobar0" << std::endl;
-
-        if (!receiveJson(recvbuf)) {
-          keep_reading = false;
-
-          std::cout << "fail1" << std::endl;
-
-          continue;
-        }
-
-        std::cout << "foobar1" << std::endl;
-
-        if (!parseJson(&root, recvbuf)) {
-          std::cerr << "received invalid JSON from server" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-        std::cout << "foobar2" << std::endl;
-        std::cout << root << std::endl;
-
-        if (root["cancel"].asBool()) {
-          std::cerr << "server cancelled recieving file" << std::endl;
-          keep_reading = false;
-          continue;
-        }
-
-        writebuf = base64::decodeVector(root["data"].asString());
-        fs.write(writebuf.data(), writebuf.size());
-        remaining = root["remaining"].asInt();
-
-        std::cout << "remaining == " << remaining << std::endl;
-
-        recvAck["received"] = recvCount++;
-
-        if (!sendJson(recvAck)) {
-          continue;
-        }
-      }
-
-      fs.close();
-      break;
-    }
-    case CMD_LIST: {
-      boost::asio::streambuf recvbuf;
-      Json::Value root, recv;
-      //       const char *recvjson;
-
-      int chunks, rec = 0;
-      root["command"] = "list";
-      if (!sendJson(root)) {
-        std::cerr << "couldn't send json" << std::endl;
-        continue;
-      }
-      if (!receiveJson(recvbuf)) {
-        std::cerr << "couldn't receive json" << std::endl;
-        continue;
-      }
-      if (!parseJson(&recv, recvbuf)) {
-        std::cerr << "couldn't parse json" << std::endl;
-        continue;
-      }
-
-      if (recv["accept"].asBool()) {
-        // ACK?
-        root["command"] = "list";
-        root["cancel"] = false;
-        chunks = std::numeric_limits<int>::max();
-        while (chunks) {
-          if (!receiveJson(recvbuf)) {
-            std::cerr << "couldn't receive json" << std::endl;
-            break;
-          }
-          if (!parseJson(&recv, recvbuf)) {
-            std::cerr << "couldn't parse json" << std::endl;
-            break;
-          }
-          // dump received data
-          if (recv["names"].isArray()) {
-            for (Json::Value name : recv["names"]) {
-              std::cout << name.asString();
-            }
-          } else {
-            std::cerr << "should have recieved an array of files, but did not "
-                         "receive an array"
-                      << std::endl;
-          }
-          chunks = recv["remaining"].asInt();
-          root["received"] = rec;
-          if (!sendJson(root)) {
-            // couldnt ACK
-            break;
-          }
-          rec++;
-        }
-      } else {
-        std::cerr << "server refused listing" << std::endl;
-      }
-      break;
-    }
-    case CMD_DISCONNECT: {
-      std::printf("disconnecting\n");
-      keep_reading = false;
-      break;
-    }
-    default: {
-      std::printf("unknown command %s\n", line);
-    }
-    case CMD_HELP: {
-      std::printf("\n");
-      std::printf("AVAILABLE COMMANDS are:\n");
-      printCmds();
-      break;
-    }
-    }
-    std::printf("\n");
-  }
-  free(line);
-  rl_clear_history();
-
-  return;
-}

+ 4 - 3
daemon/src/base64.cpp

@@ -18,9 +18,10 @@ std::vector<char> base64::decodeVector(const std::string &val) {
   using namespace boost::archive::iterators;
   using It =
       transform_width<binary_from_base64<std::string::const_iterator>, 8, 6>;
-  return boost::algorithm::trim_right_copy_if(
-      std::vector<char>(It(std::begin(val)), It(std::end(val))),
-      [](char c) { return c == '\0'; });
+  std::vector<char> ret =
+      std::vector<char>(It(std::begin(val)), It(std::end(val)));
+  ret.pop_back();
+  return ret;
 }
 
 std::string base64::encode(const std::string &val) {

+ 8 - 0
gui/src/main.cpp

@@ -29,14 +29,22 @@ int main(int argc, char *argv[]) {
 
   QMLHandler qmlHandler;
 
+  // Set the context for the window, so that the qml files can be connected to
+  // the qmlHandler
   engine.rootContext()->setContextProperty("_qmlHandler", &qmlHandler);
 
+  // Load the main window
   QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml")));
 
   QObject *object = component.create();
 
+  // Run the main window and save the return of the application in an integer
+  // ret This is blocking, which means it will block the main program until the
+  // window is closed
   int ret = app.exec();
 
+  // If we land here, the window has been closed. Properly disconnect from the
+  // server now
   qmlHandler.onExit();
 
   return ret;

+ 36 - 9
gui/src/qmlhandler.cpp

@@ -31,32 +31,37 @@ void QMLHandler::onExit() {
   write(outpipefd[1], "disconnect\n", strlen("disconnect\n"));
 }
 
+// This method gets a string and tries to read it as Json
+// If it fails to do so, return and do nothing, else handle the content
 void QMLHandler::handleJSON(string buffer) {
   Json::Value root;
   Json::CharReaderBuilder builder;
   Json::CharReader *reader = builder.newCharReader();
   string jsonError;
 
+  // Try to parse the string as Json and store the result of the pasring in a
+  // boolean
   bool parsingSuccessful = reader->parse(
       buffer.c_str(), buffer.c_str() + buffer.size(), &root, &jsonError);
 
+  // If the string is not correct Json, return
   if (!parsingSuccessful) {
     return;
   }
+
   const Json::Value command = root["command"];
   string cmd = command.asString();
 
-  if (cmd == "status") {
+  if (cmd.compare("status")) {
     emit footerSetStatus(root["response"].asString().c_str());
   }
 
-  else if (cmd == "close") {
+  else if (cmd.compare("close")) {
     programActive = false;
   }
 
-  else if (cmd == "list") {
+  else if (cmd.compare("listdata")) {
     emit receivingClearFileList();
-
     // Get the array of file Names
     auto fileNames = root["names"];
     for (int i = 0; i < fileNames.size(); i++) {
@@ -64,8 +69,18 @@ void QMLHandler::handleJSON(string buffer) {
           QString::fromStdString(fileNames[i].asString().c_str()));
     }
   }
+
+  else if (cmd.compare("version")) {
+    // TODO: Change hardcoded login details to input values
+    write(outpipefd[1], "user\n", strlen("user\n"));
+    write(outpipefd[1], "pass\n", strlen("pass\n"));
+  }
 }
 
+// This method is a loop which runs in a seperate thread.
+// If will read the Input Pipe, which containts the string that get printed
+// on stdout by the CLI/Server, and calls handleJSON with the read content
+// one it reads a newline.
 void QMLHandler::readPipeLoop() {
   unsigned int readOffset = 0;
   unsigned int pollCount = 0;
@@ -79,18 +94,22 @@ void QMLHandler::readPipeLoop() {
     if (inPipeStatus.revents & POLLIN) {
       readOffset += read(inpipefd[0], buf + readOffset, 1);
 
+      if (buf[readOffset - 1] == '\n') {
+        pollCount = 10;
+      } else {
+        pollCount = 0;
+      }
+
       pollCount = 0;
 
     } else {
       pollCount++;
     }
 
-    if (pollCount > 9 && buf[0]) {
-      buf[1024] = 0;
-      buf[strlen(buf)] = 0;
+    if (pollCount > 9 && readOffset > 0) {
+      buf[strnlen(buf, 1024)] = 0;
 
-      string cleanBuffer = buf + strcspn(buf, "\n") + 1;
-      string receivedData = cleanBuffer.substr(0, cleanBuffer.size() - 1);
+      string receivedData = buf;
 
       emit log(QString::fromStdString(receivedData));
       qInfo() << QString::fromStdString(receivedData);
@@ -99,6 +118,14 @@ void QMLHandler::readPipeLoop() {
       pollCount = 0;
       readOffset = 0;
     }
+
+    // Fixme
+    if (readOffset >= 1024) {
+      qInfo() << "Fixme: QMLHandler::readPipeLoop() readOffset too high!!!";
+      readOffset = 0;
+      pollCount = 0;
+      memset(buf, 0, 1025);
+    }
   }
 }