diff --git a/applications/gui2/src/modules/addsource.cpp b/applications/gui2/src/modules/addsource.cpp
index f584274c5fb9de9dec745709481ea663506be4f5..897af5be79658ffb4bc0ac1c951e14bbb636f51e 100644
--- a/applications/gui2/src/modules/addsource.cpp
+++ b/applications/gui2/src/modules/addsource.cpp
@@ -29,6 +29,10 @@ std::vector<std::string> AddCtrl::getNetSources() {
 	return std::move(io->feed()->availableNetworkSources());
 }
 
+std::vector<std::string> AddCtrl::getFileSources() {
+	return std::move(io->feed()->availableFileSources());
+}
+
 std::string AddCtrl::getSourceName(const std::string &uri) {
 	return io->feed()->getName(uri);
 }
diff --git a/applications/gui2/src/modules/addsource.hpp b/applications/gui2/src/modules/addsource.hpp
index e243a2ec2f793c27a764a289d62a79469d789a1c..25064abe8a420b5ff3950b4728b16a871609a6c8 100644
--- a/applications/gui2/src/modules/addsource.hpp
+++ b/applications/gui2/src/modules/addsource.hpp
@@ -22,6 +22,7 @@ public:
 	ftl::Configurable *add(const std::string &uri);
 
 	std::vector<std::string> getNetSources();
+	std::vector<std::string> getFileSources();
 	std::string getSourceName(const std::string &uri);
 	bool isSourceActive(const std::string &uri);
 
diff --git a/applications/gui2/src/views/addsource.cpp b/applications/gui2/src/views/addsource.cpp
index c67f3e5f662ce9326b1e23fa381bb688b18214da..a1115d743f14b8160e0e46d02e71eefd63b928e9 100644
--- a/applications/gui2/src/views/addsource.cpp
+++ b/applications/gui2/src/views/addsource.cpp
@@ -83,6 +83,20 @@ void AddSourceWindow::rebuild() {
                     { {"ftl", "FTL Captures"} }, true));
 		close();
 	});
+
+	auto filesrcs = ctrl_->getFileSources();
+
+	for (auto &s : filesrcs) {
+		button = new Button(filebuttons, ctrl_->getSourceName(s));
+		if (ctrl_->isSourceActive(s)) {
+			button->setBackgroundColor(Color(0, 255, 0, 25));
+		}
+
+		button->setCallback([this, s]() {
+			ctrl_->add(s);
+			close();
+		});
+	}
 }
 
 void AddSourceWindow::close() {
diff --git a/components/common/cpp/include/ftl/configuration.hpp b/components/common/cpp/include/ftl/configuration.hpp
index 1111e229e1f2564d8af4de9878a1af1e8c18523d..7ad81440b1abdcd4d4d2d6cf4a68d7be55be7c1c 100644
--- a/components/common/cpp/include/ftl/configuration.hpp
+++ b/components/common/cpp/include/ftl/configuration.hpp
@@ -24,6 +24,10 @@ bool create_directory(const std::string &path);
 bool is_video(const std::string &file);
 std::vector<std::string> directory_listing(const std::string &path);
 
+nlohmann::json loadJSON(const std::string &path);
+
+bool saveJSON(const std::string &path, nlohmann::json &json);
+
 namespace config {
 
 typedef nlohmann::json json_t;
diff --git a/components/common/cpp/src/configuration.cpp b/components/common/cpp/src/configuration.cpp
index 11115402284a3a0ad5db5e15f48c6eade77141b1..5e7bd0ea23122034eefbb71292318ac105f63bf5 100644
--- a/components/common/cpp/src/configuration.cpp
+++ b/components/common/cpp/src/configuration.cpp
@@ -33,6 +33,7 @@
 
 using ftl::config::json_t;
 using std::ifstream;
+using std::ofstream;
 using std::string;
 using std::map;
 using std::vector;
@@ -175,6 +176,42 @@ optional<string> ftl::config::locateFile(const string &name) {
 	return {};
 }
 
+nlohmann::json ftl::loadJSON(const std::string &path) {
+	ifstream i(path.c_str());
+	//i.open(path);
+	if (i.is_open()) {
+		try {
+			nlohmann::json t;
+			i >> t;
+			return t;
+		} catch (nlohmann::json::parse_error& e) {
+			LOG(ERROR) << "Parse error in loading JSON: "  << e.what();
+			return {};
+		} catch (...) {
+			LOG(ERROR) << "Unknown error opening JSON file: " << path;
+		}
+		return {};
+	} else {
+		return {};
+	}
+}
+
+bool ftl::saveJSON(const std::string &path, nlohmann::json &json) {
+	ofstream o(path.c_str());
+	//i.open(path);
+	if (o.is_open()) {
+		try {
+			o << json;
+			return true;
+		} catch (...) {
+			LOG(ERROR) << "Unknown error saving JSON file: " << path;
+		}
+		return false;
+	} else {
+		return false;
+	}
+}
+
 /**
  * Combine one json config with another patch json config.
  */
diff --git a/components/streams/include/ftl/streams/feed.hpp b/components/streams/include/ftl/streams/feed.hpp
index cf24ffa61cf3e15e3acd7881f0b1a83122307b25..0cb76a61452b87749ec9843a4bdd7d9d4bed4507 100644
--- a/components/streams/include/ftl/streams/feed.hpp
+++ b/components/streams/include/ftl/streams/feed.hpp
@@ -54,7 +54,6 @@ public:
 	};
 
 private:
-
 	// public methods acquire lock if necessary, private methods assume locking
 	// managed by caller
 	std::mutex mtx_;
diff --git a/components/streams/src/feed.cpp b/components/streams/src/feed.cpp
index 7891818a9eface84487496cbfc23ebbdd2724cd2..6c294181691ac0edd32dd79750442a03fd099310 100644
--- a/components/streams/src/feed.cpp
+++ b/components/streams/src/feed.cpp
@@ -9,6 +9,8 @@
 using ftl::stream::Feed;
 using ftl::codecs::Channel;
 
+static nlohmann::json feed_config;
+
 ////////////////////////////////////////////////////////////////////////////////
 
 Feed::Filter::Filter(Feed* feed, const std::unordered_set<uint32_t>& sources, const std::unordered_set<Channel>& channels) :
@@ -46,6 +48,8 @@ Feed::Filter &Feed::Filter::select(const std::unordered_set<ftl::codecs::Channel
 Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 		ftl::Configurable(config), net_(net) {
 
+	feed_config = ftl::loadJSON(FTL_LOCAL_CONFIG_ROOT "/feed.json");
+
 	pool_ = std::make_unique<ftl::data::Pool>(3,5);
 
 	stream_ = std::unique_ptr<ftl::stream::Muxer>
@@ -142,6 +146,8 @@ Feed::Feed(nlohmann::json &config, ftl::net::Universe*net) :
 
 Feed::~Feed() {
 	std::unique_lock<std::mutex> lk(mtx_);
+	ftl::saveJSON(FTL_LOCAL_CONFIG_ROOT "/feed.json", feed_config);
+
 	receiver_.reset();  // Note: Force destruction first to remove filters this way
 
 	for (auto* filter : filters_) {
@@ -285,7 +291,14 @@ std::vector<std::string> Feed::availableNetworkSources() {
 }
 
 std::vector<std::string> Feed::availableFileSources() {
-	return {};
+	std::vector<std::string> files;
+	auto &recent_files = feed_config["recent_files"];
+
+	for (auto &f : recent_files.items()) {
+		files.push_back(f.key());
+	}
+
+	return files;
 }
 
 std::vector<std::string> Feed::availableDeviceSources() {
@@ -319,7 +332,7 @@ std::string Feed::getName(const std::string &puri) {
 	} else if (uri.getScheme() == ftl::URI::SCHEME_DEVICE) {
 		return "Device";
 	} else if (uri.getScheme() == ftl::URI::SCHEME_FILE) {
-		// TODO: Parse last part of file name - extension
+		return feed_config["recent_files"][uri.getBaseURI()].value("name", "FTLFile");
 	}
 
 	return "No Name";
@@ -365,6 +378,12 @@ uint32_t Feed::add(const std::string &path) {
 			fstream->set("filename", uri.getPath());
 		}
 
+		auto &recent_files = feed_config["recent_files"];
+		auto &file_details = recent_files[uri.getBaseURI()];
+		std::string fname = uri.getPathSegment(-1);
+		file_details["name"] = fname.substr(0, fname.find_last_of('.'));
+		file_details["last_open"] = ftl::timer::get_time();
+
 		// 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