diff --git a/applications/gui2/src/modules/addsource.cpp b/applications/gui2/src/modules/addsource.cpp index 2acdd22750db78902e2c4647248b48a31fa1da2b..c47599066994c4d738c8227175791a8184d772cb 100644 --- a/applications/gui2/src/modules/addsource.cpp +++ b/applications/gui2/src/modules/addsource.cpp @@ -25,6 +25,14 @@ ftl::Configurable *AddCtrl::add(const std::string &uri) { return nullptr; } +std::vector<std::string> AddCtrl::getHosts() { + return std::move(io->feed()->knownHosts()); +} + +std::set<ftl::stream::SourceInfo> AddCtrl::getRecent() { + return std::move(io->feed()->recentSources()); +} + std::vector<std::string> AddCtrl::getNetSources() { return std::move(io->feed()->availableNetworkSources()); } diff --git a/applications/gui2/src/modules/addsource.hpp b/applications/gui2/src/modules/addsource.hpp index 51acefbf609167f8f01b32b984370a385ba63335..7e6570bb4357793281ce102bec93ad48865f1d45 100644 --- a/applications/gui2/src/modules/addsource.hpp +++ b/applications/gui2/src/modules/addsource.hpp @@ -21,6 +21,8 @@ public: ftl::Configurable *add(const std::string &uri); + std::vector<std::string> getHosts(); + std::set<ftl::stream::SourceInfo> getRecent(); std::vector<std::string> getNetSources(); std::vector<std::string> getFileSources(); std::vector<std::string> getDeviceSources(); diff --git a/applications/gui2/src/modules/themes.cpp b/applications/gui2/src/modules/themes.cpp index 4576687c66c47b18b0da331dff4c97f15ae1ae3b..ee711a68657f051c012ee8bdb1c9af328c67a642 100644 --- a/applications/gui2/src/modules/themes.cpp +++ b/applications/gui2/src/modules/themes.cpp @@ -57,15 +57,24 @@ void Themes::init() { viewtheme->mWindowDropShadowSize = 0; auto* windowtheme_dark = screen->getTheme("window_dark"); - windowtheme_dark->mWindowCornerRadius = 2; - windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(90,255); + windowtheme_dark->mWindowCornerRadius = 5; + /*windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(90,255); windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(70,255); windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(110,255); windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(110,255); windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(50,255); - windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255); + windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(90,255);*/ + windowtheme_dark->mButtonGradientBotFocused = nanogui::Color(60,255); + windowtheme_dark->mButtonGradientBotUnfocused = nanogui::Color(30,30,40,180); + windowtheme_dark->mButtonGradientTopFocused = nanogui::Color(60,255); + windowtheme_dark->mButtonGradientTopUnfocused = nanogui::Color(30,30,40,180); + windowtheme_dark->mButtonGradientTopPushed = nanogui::Color(60,180); + windowtheme_dark->mButtonGradientBotPushed = nanogui::Color(60,180); windowtheme_dark->mButtonFontSize = 16; windowtheme_dark->mIconScale = 0.85f; + windowtheme_dark->mBorderDark = nanogui::Color(90,255); + windowtheme_dark->mBorderMedium = nanogui::Color(90,255); + windowtheme_dark->mBorderLight = nanogui::Color(90,255); auto* mediatheme = screen->getTheme("media"); mediatheme->mIconScale = 1.2f; diff --git a/applications/gui2/src/views/addsource.cpp b/applications/gui2/src/views/addsource.cpp index 1f962b4970b0cea566bc1d557188175fd7cd5ae1..321a1ccf153f95d2df0ecc6a99613c8e9dc38311 100644 --- a/applications/gui2/src/views/addsource.cpp +++ b/applications/gui2/src/views/addsource.cpp @@ -6,6 +6,8 @@ #include <nanogui/layout.h> #include <nanogui/label.h> #include <nanogui/button.h> +#include <nanogui/vscrollpanel.h> +#include <nanogui/tabwidget.h> #include <loguru.hpp> @@ -13,13 +15,19 @@ using ftl::gui2::AddSourceWindow; AddSourceWindow::AddSourceWindow(nanogui::Widget* parent, AddCtrl *ctrl) : - nanogui::Window(parent, "Add Source"), ctrl_(ctrl) { + nanogui::Window(parent, ""), ctrl_(ctrl) { using namespace nanogui; - //setFixedWidth(300); - setLayout(new GroupLayout(15, 6, 14, 10)); - setPosition(Vector2i(parent->width()/2.0f - 100.0f, parent->height()/2.0f - 100.0f)); + auto t = dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark"); + setTheme(t); + + //setFixedWidth(500); + setFixedSize(Vector2i(500,300)); + setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 20, 10)); + + setPosition(Vector2i(parent->width()/2.0f - fixedWidth()/2.0f, parent->height()/2.0f - fixedHeight()/2.0f)); auto close = new nanogui::Button(buttonPanel(), "", ENTYPO_ICON_CROSS); close->setTheme(dynamic_cast<ftl::gui2::Screen*>(screen())->getTheme("window_dark")); @@ -40,28 +48,149 @@ AddSourceWindow::~AddSourceWindow() { } +nanogui::Button *AddSourceWindow::_addButton(const std::string &s, nanogui::Widget *parent) { + using namespace nanogui; + + ftl::URI uri(s); + int icon = 0; + switch (uri.getScheme()) { + case ftl::URI::SCHEME_DEVICE : icon = ENTYPO_ICON_CAMERA; break; + case ftl::URI::SCHEME_FILE : icon = ENTYPO_ICON_FOLDER_VIDEO; break; + case ftl::URI::SCHEME_FTL : icon = ENTYPO_ICON_HOME; break; + case ftl::URI::SCHEME_WS : + case ftl::URI::SCHEME_TCP : icon = ENTYPO_ICON_CLASSIC_COMPUTER; break; + default: break; + } + + auto *button = new Button(parent, ctrl_->getSourceName(s), icon); + if (ctrl_->isSourceActive(s)) { + button->setBackgroundColor(Color(0, 255, 0, 25)); + } + + button->setIconPosition(Button::IconPosition::Left); + button->setIconExtraScale(1.2); + button->setFontSize(18); + button->setTooltip(s); + + button->setCallback([this, uri = s]() { + ctrl_->add(uri); + close(); + }); + + return button; +} + void AddSourceWindow::rebuild() { using namespace nanogui; - new Label(this, "Device Sources", "sans-bold"); + auto *title = new Label(this, "Add Source", "sans-bold"); + title->setFontSize(28); + + auto *tabs = new TabWidget(this); + + auto *recent_tab = tabs->createTab("Recent"); + recent_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + VScrollPanel *vscroll = new VScrollPanel(recent_tab); + vscroll->setFixedHeight(200); + Widget *recentscroll = new Widget(vscroll); + recentscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); - auto *devicebuttons = new Widget(this); - devicebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4)); Button *button; + auto srcs = ctrl_->getRecent(); + + for (auto &s : srcs) { + _addButton(s.uri, recentscroll); + } + + auto *dev_tab = tabs->createTab("Devices"); + dev_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + vscroll = new VScrollPanel(dev_tab); + vscroll->setFixedHeight(150); + Widget *devscroll = new Widget(vscroll); + devscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + auto devsrcs = ctrl_->getDeviceSources(); for (auto &s : devsrcs) { - button = new Button(devicebuttons, ctrl_->getSourceName(s)); - if (ctrl_->isSourceActive(s)) { - button->setBackgroundColor(Color(0, 255, 0, 25)); + _addButton(s, devscroll); + } + + auto *host_tab = tabs->createTab("Hosts"); + host_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + vscroll = new VScrollPanel(host_tab); + vscroll->setFixedHeight(150); + Widget *hostscroll = new Widget(vscroll); + hostscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + + auto hostsrcs = ctrl_->getHosts(); + + for (auto &s : hostsrcs) { + _addButton(s, hostscroll); + } + + auto *stream_tab = tabs->createTab("Streams"); + stream_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + vscroll = new VScrollPanel(stream_tab); + vscroll->setFixedHeight(150); + Widget *streamscroll = new Widget(vscroll); + streamscroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + + auto streamsrcs = ctrl_->getNetSources(); + + for (auto &s : streamsrcs) { + _addButton(s, streamscroll); + } + + auto *file_tab = tabs->createTab("Files"); + file_tab->setLayout(new nanogui::BoxLayout + (nanogui::Orientation::Vertical, nanogui::Alignment::Fill, 0, 0)); + vscroll = new VScrollPanel(file_tab); + vscroll->setFixedHeight(150); + Widget *filescroll = new Widget(vscroll); + filescroll->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 10, 4)); + + button = new Button(filescroll, "Open", ENTYPO_ICON_FOLDER); + button->setIconPosition(Button::IconPosition::Left); + button->setIconExtraScale(1.2); + button->setFontSize(18); + button->setTooltip("Open FTL File"); + button->setCallback([this]() { + try { + std::string filename = file_dialog({ {"ftl", "FTL Captures"} }, false); + if (filename.size() > 0 && filename[0] == '/') { + filename = std::string("file://") + filename; + } else { + filename = std::string("file:///") + filename; + } +#ifdef WIN32 + auto p = filename.find_first_of('\\'); + while (p != std::string::npos) { + filename[p] = '/'; + p = filename.find_first_of('\\'); + } +#endif + ctrl_->add(filename); + } catch (const std::exception &e) { + LOG(ERROR) << "File load exception: " << e.what(); } + close(); + }); - button->setCallback([this, s]() { - ctrl_->add(s); - close(); - }); + auto filesrcs = ctrl_->getFileSources(); + + for (auto &s : filesrcs) { + _addButton(s, filescroll); } + + + tabs->setActiveTab(0); + + /*button = new Button(devicebuttons, "Camera(s)"); button->setCallback([this]() { ctrl_->add("device:camera"); close(); }); button = new Button(devicebuttons, "Realsense"); @@ -73,7 +202,7 @@ void AddSourceWindow::rebuild() { button = new Button(devicebuttons, "OpenVR"); button->setCallback([this]() { ctrl_->add("device:openvr"); close(); });*/ - new Label(this, "Network Sources", "sans-bold"); + /*new Label(this, "Network Sources", "sans-bold"); auto *netbuttons = new Widget(this); netbuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4)); @@ -89,36 +218,15 @@ void AddSourceWindow::rebuild() { ctrl_->add(s); close(); }); - } + }*/ - new Label(this, "Files", "sans-bold"); + //new Label(this, "Files", "sans-bold"); - auto *filebuttons = new Widget(this); - filebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4)); - button = new Button(filebuttons, "", ENTYPO_ICON_FOLDER); - button->setCallback([this]() { - try { - std::string filename = file_dialog({ {"ftl", "FTL Captures"} }, false); - if (filename.size() > 0 && filename[0] == '/') { - filename = std::string("file://") + filename; - } else { - filename = std::string("file:///") + filename; - } -#ifdef WIN32 - auto p = filename.find_first_of('\\'); - while (p != std::string::npos) { - filename[p] = '/'; - p = filename.find_first_of('\\'); - } -#endif - ctrl_->add(filename); - } catch (const std::exception &e) { - LOG(ERROR) << "File load exception: " << e.what(); - } - close(); - }); + //auto *filebuttons = new Widget(this); + //filebuttons->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Middle, 0, 4)); + - auto filesrcs = ctrl_->getFileSources(); + /*auto filesrcs = ctrl_->getFileSources(); for (auto &s : filesrcs) { button = new Button(filebuttons, ctrl_->getSourceName(s)); @@ -130,7 +238,7 @@ void AddSourceWindow::rebuild() { ctrl_->add(s); close(); }); - } + }*/ } void AddSourceWindow::close() { diff --git a/applications/gui2/src/views/addsource.hpp b/applications/gui2/src/views/addsource.hpp index 4d340f856c832fd28450adf94c43907c423b1b87..938e1e3dde73d87b484814d8b98f731394045a0f 100644 --- a/applications/gui2/src/views/addsource.hpp +++ b/applications/gui2/src/views/addsource.hpp @@ -25,6 +25,8 @@ private: void close(); void rebuild(); + nanogui::Button *_addButton(const std::string &s, nanogui::Widget *parent); + ftl::Handle new_source_handle_; MUTEX mutex_; std::atomic_bool do_rebuild_=false; diff --git a/components/common/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp index 65ac79a97d1f0336f4132e54095a8cc726736b93..3a80439fca816ace6ecec81cfc80af921c3b848a 100644 --- a/components/common/cpp/include/ftl/uri.hpp +++ b/components/common/cpp/include/ftl/uri.hpp @@ -51,9 +51,9 @@ namespace ftl { * Get the URI without query parameters, and limit path to length N. * If N is negative then it is taken from full path length. */ - std::string getBaseURI(int n); + std::string getBaseURI(int n) const; - std::string getBaseURIWithUser(); + std::string getBaseURIWithUser() const; std::string getPathSegment(int n) const; @@ -61,11 +61,13 @@ namespace ftl { void setAttribute(const std::string &key, int value); template <typename T> - T getAttribute(const std::string &key) { + T getAttribute(const std::string &key) const { auto i = m_qmap.find(key); return (i != m_qmap.end()) ? T(i->second) : T(); } + bool hasAttribute(const std::string &a) const { return m_qmap.count(a) > 0; } + std::string to_string() const; void to_json(nlohmann::json &); @@ -89,13 +91,13 @@ namespace ftl { }; template <> - inline int URI::getAttribute<int>(const std::string &key) { + inline int URI::getAttribute<int>(const std::string &key) const { auto i = m_qmap.find(key); return (i != m_qmap.end()) ? std::stoi(i->second) : 0; } template <> - inline std::string URI::getAttribute<std::string>(const std::string &key) { + inline std::string URI::getAttribute<std::string>(const std::string &key) const { auto i = m_qmap.find(key); return (i != m_qmap.end()) ? i->second : ""; } diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp index 37a5d579215faecbb3fa0be36391ed986e6e52f1..9311b841b2fd227d16f63c572b0ba5dcb80a454c 100644 --- a/components/common/cpp/src/uri.cpp +++ b/components/common/cpp/src/uri.cpp @@ -160,7 +160,7 @@ string URI::getPathSegment(int n) const { else return m_pathseg[N]; } -string URI::getBaseURI(int n) { +string URI::getBaseURI(int n) const { if (n >= (int)m_pathseg.size()) return m_base; if (n >= 0) { string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : ""); @@ -182,7 +182,7 @@ string URI::getBaseURI(int n) { } else return ""; } -std::string URI::getBaseURIWithUser() { +std::string URI::getBaseURIWithUser() const { std::string result; result += m_protostr + "://"; diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp index 95642070546cdd185565055df87cf8d2518d42cf..23d500b61547643e930f2e5eea340d5dd2366060 100644 --- a/components/streams/include/ftl/streams/feed.hpp +++ b/components/streams/include/ftl/streams/feed.hpp @@ -21,6 +21,13 @@ namespace ftl { namespace render { class Source; } namespace stream { +struct SourceInfo { + std::string uri; + int64_t last_used; + + inline bool operator<(const SourceInfo &o) const { return last_used > o.last_used; } +}; + class Feed : public ftl::Configurable { public: /** @@ -66,69 +73,6 @@ public: std::vector<ftl::Handle> handles_; }; -private: - // public methods acquire lock if necessary, private methods assume locking - // managed by caller - SHARED_MUTEX mtx_; - std::condition_variable cv_net_connect_; - - ftl::net::Universe* const net_; - std::unique_ptr<ftl::data::Pool> pool_; - std::unique_ptr<ftl::stream::Intercept> interceptor_; - // multiple streams to single fs - std::unique_ptr<ftl::stream::Muxer> stream_; - - // streams to fs - std::unique_ptr<ftl::stream::Receiver> receiver_; - ftl::Handle handle_receiver_; - - // framesets to stream - std::unique_ptr<ftl::stream::Sender> sender_; - ftl::Handle handle_sender_; - - std::unique_ptr<ftl::stream::Sender> recorder_; - std::unique_ptr<ftl::stream::Broadcast> record_stream_; - ftl::Handle handle_record_; - ftl::Handle record_recv_handle_; - Filter *record_filter_; - - //ftl::Handler<const ftl::data::FrameSetPtr&> frameset_cb_; - std::unordered_map<std::string, uint32_t> fsid_lookup_; - std::map<uint32_t, ftl::data::FrameSetPtr> latest_; - std::unordered_map<std::string, uint32_t> groups_; - - std::unordered_map<uint32_t, std::list<ftl::stream::Stream*>> streams_; - std::unordered_map<uint32_t, ftl::rgbd::Source*> devices_; - std::unordered_map<uint32_t, ftl::render::Source*> renderers_; - std::unordered_map<uint32_t, ftl::operators::Graph*> pre_pipelines_; - std::list<ftl::streams::ManualSourceBuilder*> render_builders_; - - std::vector<std::string> netcams_; - ftl::Handler<uint32_t> new_sources_cb_; - ftl::Handler<uint32_t> add_src_cb_; - ftl::Handler<uint32_t> remove_sources_cb_; - - std::vector<Filter*> filters_; - - uint32_t fs_counter_ = 0; - uint32_t file_counter_ = 0; - - uint32_t allocateFrameSetId(const std::string &group); - - void add(uint32_t fsid, const std::string &uri, ftl::stream::Stream *s); - - /** callback for network (adds new sorces on connect/...) */ - void updateNetSources(); - /** select channels and sources based on current filters_; */ - void select(); - - void _createPipeline(uint32_t fsid); - ftl::operators::Graph* _addPipeline(uint32_t fsid); - - void _beginRecord(Filter *f); - void _stopRecording(); - bool _isRecording(); - public: Feed(nlohmann::json &config, ftl::net::Universe *net); @@ -158,6 +102,8 @@ public: std::vector<ftl::data::FrameID> listFrames(); std::vector<unsigned int> listFrameSets(); + std::set<ftl::stream::SourceInfo> recentSources(); + std::vector<std::string> knownHosts(); std::vector<std::string> availableNetworkSources(); std::vector<std::string> availableFileSources(); std::vector<std::string> availableDeviceSources(); @@ -198,6 +144,71 @@ public: void removeFilter(Filter* filter); void autoConnect(); + +private: + // public methods acquire lock if necessary, private methods assume locking + // managed by caller + SHARED_MUTEX mtx_; + std::condition_variable cv_net_connect_; + + ftl::net::Universe* const net_; + std::unique_ptr<ftl::data::Pool> pool_; + std::unique_ptr<ftl::stream::Intercept> interceptor_; + // multiple streams to single fs + std::unique_ptr<ftl::stream::Muxer> stream_; + + // streams to fs + std::unique_ptr<ftl::stream::Receiver> receiver_; + ftl::Handle handle_receiver_; + + // framesets to stream + std::unique_ptr<ftl::stream::Sender> sender_; + ftl::Handle handle_sender_; + + std::unique_ptr<ftl::stream::Sender> recorder_; + std::unique_ptr<ftl::stream::Broadcast> record_stream_; + ftl::Handle handle_record_; + ftl::Handle record_recv_handle_; + Filter *record_filter_; + + //ftl::Handler<const ftl::data::FrameSetPtr&> frameset_cb_; + std::unordered_map<std::string, uint32_t> fsid_lookup_; + std::map<uint32_t, ftl::data::FrameSetPtr> latest_; + std::unordered_map<std::string, uint32_t> groups_; + + std::unordered_map<uint32_t, std::list<ftl::stream::Stream*>> streams_; + std::unordered_map<uint32_t, ftl::rgbd::Source*> devices_; + std::unordered_map<uint32_t, ftl::render::Source*> renderers_; + std::unordered_map<uint32_t, ftl::operators::Graph*> pre_pipelines_; + std::list<ftl::streams::ManualSourceBuilder*> render_builders_; + + std::vector<std::string> netcams_; + ftl::Handler<uint32_t> new_sources_cb_; + ftl::Handler<uint32_t> add_src_cb_; + ftl::Handler<uint32_t> remove_sources_cb_; + + std::vector<Filter*> filters_; + + uint32_t fs_counter_ = 0; + uint32_t file_counter_ = 0; + + uint32_t allocateFrameSetId(const std::string &group); + + void add(uint32_t fsid, const std::string &uri, ftl::stream::Stream *s); + nlohmann::json &_add_recent_source(const ftl::URI &uri); + + /** callback for network (adds new sorces on connect/...) */ + void _updateNetSources(ftl::net::Peer *p); + void _updateNetSources(ftl::net::Peer *p, const std::string &uri); + /** select channels and sources based on current filters_; */ + void select(); + + void _createPipeline(uint32_t fsid); + ftl::operators::Graph* _addPipeline(uint32_t fsid); + + void _beginRecord(Filter *f); + void _stopRecording(); + bool _isRecording(); }; } diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp index 8a2eb4a2840a95f726b0b6db77d2534da943cb90..47c388ca3d64ad4e14e620da7ef4fa3a6eb5c745 100644 --- a/components/streams/src/feed.cpp +++ b/components/streams/src/feed.cpp @@ -93,6 +93,7 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) : //feed_config = ftl::loadJSON(FTL_LOCAL_CONFIG_ROOT "/feed.json"); restore(ftl::Configurable::getID(), { "recent_files", + "recent_sources", "known_hosts", "auto_host_connect", "auto_host_sources", @@ -147,18 +148,14 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) : }); net_->onConnect([this](ftl::net::Peer *p) { - ftl::pool.push([this](int id) { - // FIXME: Find better option that waiting here. - // Wait to make sure streams have started properly. - std::this_thread::sleep_for(std::chrono::milliseconds(100)); - //UNIQUE_LOCK(mtx_, lk); - updateNetSources(); + ftl::pool.push([this,p](int id) { + _updateNetSources(p); }); }); - net_->bind("add_stream", [this](std::string uri){ + net_->bind("add_stream", [this](ftl::net::Peer &p, std::string uri){ //UNIQUE_LOCK(mtx_, lk); - updateNetSources(); + _updateNetSources(&p, uri); }); net_->onDisconnect([this](ftl::net::Peer *) { @@ -215,7 +212,7 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) : stream_->begin(); - if (value("auto_host_connect", true)) autoConnect(); + //if (value("auto_host_connect", true)) autoConnect(); } Feed::~Feed() { @@ -466,16 +463,37 @@ void Feed::removeFilter(Feed::Filter* filter) { } } -void Feed::updateNetSources() { - auto netcams = - net_->findAll<std::string>("list_streams"); +void Feed::_updateNetSources(ftl::net::Peer *p, const std::string &s) { + UNIQUE_LOCK(mtx_, lk); + netcams_.push_back(s); + + // TODO: Auto add source + + ftl::URI uri(s); + _add_recent_source(uri)["host"] = p->getURI(); + + ftl::pool.push([this](int id) { + new_sources_cb_.trigger(0); + }); +} + +void Feed::_updateNetSources(ftl::net::Peer *p) { + //auto netcams = + // net_->findAll<std::string>("list_streams"); + + auto peerstreams = p->call<std::vector<std::string>>("list_streams"); UNIQUE_LOCK(mtx_, lk); - netcams_ = std::move(netcams); + //netcams_ = std::move(netcams); + netcams_.insert(netcams_.end(), peerstreams.begin(), peerstreams.end()); - if (value("auto_host_sources", false)) { + for (const auto &s : peerstreams) { + ftl::URI uri(s); + _add_recent_source(uri)["host"] = p->getURI(); + } + + /*if (value("auto_host_sources", false)) { for (auto s : netcams_) { - ftl::URI uri(s); const std::string group = uri.getAttribute<std::string>("group"); if (fsid_lookup_.count(uri.getBaseURI()) == 0) { @@ -498,7 +516,7 @@ void Feed::updateNetSources() { LOG(INFO) << "Stream exists: " << s; } } - } + }*/ ftl::pool.push([this](int id) { new_sources_cb_.trigger(0); @@ -526,6 +544,32 @@ std::vector<std::string> Feed::availableFileSources() { return files; } +std::vector<std::string> Feed::knownHosts() { + std::vector<std::string> hosts; + auto &known = getConfig()["known_hosts"]; + + for (auto &f : known.items()) { + hosts.push_back(f.key()); + } + + return hosts; +} + +std::set<ftl::stream::SourceInfo> Feed::recentSources() { + std::set<ftl::stream::SourceInfo> result; + + auto &recent = getConfig()["recent_sources"]; + + for (auto &f : recent.items()) { + ftl::stream::SourceInfo info; + info.uri = f.key(); + info.last_used = f.value()["last_open"].get<int64_t>(); + result.insert(info); + } + + return result; +} + std::vector<std::string> Feed::availableDeviceSources() { std::vector<std::string> results; @@ -596,11 +640,29 @@ std::string Feed::getName(const std::string &puri) { return "Unknown Device"; } else if (uri.getScheme() == ftl::URI::SCHEME_FILE) { return getConfig()["recent_files"][uri.getBaseURI()].value("name", "FTLFile"); + } else if (uri.getScheme() == ftl::URI::SCHEME_TCP || uri.getScheme() == ftl::URI::SCHEME_WS) { + return uri.getBaseURI(); } return uri.getPathSegment(-1); } +nlohmann::json &Feed::_add_recent_source(const ftl::URI &uri) { + auto &known = getConfig()["recent_sources"]; + auto &details = known[uri.getBaseURI()]; + std::string name = uri.getPathSegment(-1); + + if (uri.hasAttribute("name")) { + name = uri.getAttribute<std::string>("name"); + } else if (uri.getScheme() == ftl::URI::SCHEME_FILE) { + name = name.substr(0, name.find_last_of('.')); + } + + details["name"] = name; + details["last_open"] = ftl::timer::get_time(); + return details; +} + void Feed::add(uint32_t fsid, const std::string &uri, ftl::stream::Stream* stream) { fsid_lookup_[uri] = fsid; latest_[fsid] = nullptr; @@ -660,6 +722,8 @@ uint32_t Feed::add(const std::string &path) { file_details["name"] = fname.substr(0, fname.find_last_of('.')); file_details["last_open"] = ftl::timer::get_time(); + _add_recent_source(uri); + // TODO: URI normalization; should happen in add(,,) or add(,,,) take // ftl::URI instead of std::string as argument. Note the bug above. // TODO: write unit test for uri parsing @@ -713,6 +777,8 @@ uint32_t Feed::add(const std::string &path) { creator->start(); } + _add_recent_source(uri); + add_src_cb_.trigger(fsid); return fsid; } @@ -732,6 +798,15 @@ uint32_t Feed::add(const std::string &path) { } else if (scheme == ftl::URI::SCHEME_FTL) { + // Attempt to ensure connection first + auto &known = getConfig()["recent_sources"]; + auto &details = known[uri.getBaseURI()]; + if (details.contains("host")) { + net_->connect(details["host"].get<std::string>())->waitConnection(); + } else { + // See if it can otherwise be found? + } + auto *stream = ftl::create<ftl::stream::Net> (this, std::string("netstream") +std::to_string(fsid_lookup_.size()), net_);