Switch to extracted desktop file library

This commit is contained in:
TheAssassin
2018-12-22 23:09:04 +01:00
parent 6b715c691a
commit 5374e7ece9
34 changed files with 18 additions and 2227 deletions

21
.gitmodules vendored
View File

@@ -76,24 +76,9 @@
[submodule "lib/googletest"]
path = lib/googletest
url = https://github.com/google/googletest
[submodule "lib/boost-lexical_cast"]
path = lib/boost-lexical_cast
url = https://github.com/boostorg/lexical_cast
[submodule "lib/boost-concept_check"]
path = lib/boost-concept_check
url = https://github.com/boostorg/concept_check
[submodule "lib/boost-numeric_conversion"]
path = lib/boost-numeric_conversion
url = https://github.com/boostorg/numeric_conversion
[submodule "lib/boost-array"]
path = lib/boost-array
url = https://github.com/boostorg/array
[submodule "lib/boost-container"]
path = lib/boost-container
url = https://github.com/boostorg/container
[submodule "lib/boost-move"]
path = lib/boost-move
url = https://github.com/boostorg/move
[submodule "lib/boost-math"]
path = lib/boost-math
url = https://github.com/boostorg/math
[submodule "lib/linuxdeploy-desktopfile"]
path = lib/linuxdeploy-desktopfile
url = https://github.com/linuxdeploy/linuxdeploy-desktopfile

View File

@@ -5,7 +5,7 @@
#include <boost/filesystem.hpp>
// local includes
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/desktopfile/desktopfile.h"
#pragma once

View File

@@ -1,126 +0,0 @@
// system includes
#include <unordered_map>
// library includes
#include <boost/filesystem.hpp>
// local includes
#include "desktopfileentry.h"
#pragma once
namespace linuxdeploy {
namespace core {
namespace desktopfile {
/*
* Parse and read desktop files.
*/
class DesktopFile {
public:
// describes a single section
typedef std::unordered_map<std::string, DesktopFileEntry> section_t;
// describes all sections in the desktop file
typedef std::unordered_map<std::string, section_t> sections_t;
private:
// private data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
// (in)equality operators are implemented outside this class
friend bool operator==(const DesktopFile& first, const DesktopFile& second);
friend bool operator!=(const DesktopFile& first, const DesktopFile& second);
public:
// default constructor
DesktopFile();
// construct from existing desktop file
// if the file exists, it will be read using DesktopFileReader
// if reading fails, exceptions will be thrown (see DesktopFileReader for more information)
explicit DesktopFile(const boost::filesystem::path& path);
// construct by reading an existing stream
// file must exist, otherwise std::runtime_error is thrown
explicit DesktopFile(std::istream& is);
// copy constructor
DesktopFile(const DesktopFile& other);
// copy assignment constructor
DesktopFile& operator=(const DesktopFile& other);
// move assignment operator
DesktopFile& operator=(DesktopFile&& other) noexcept;
public:
// returns true if a file has been loaded, false otherwise
bool isEmpty() const;
// read desktop file
// sets path associated with this file
// throws exceptions in case of issues, see DesktopFileReader for more information
void read(const boost::filesystem::path& path);
// read desktop file from existing stream
// throws exceptions in case of issues, see DesktopFileReader for more information
void read(std::istream& is);
// get path associated with this file
boost::filesystem::path path() const;
// sets the path associated with this desktop file
// used to e.g., save the desktop file
void setPath(const boost::filesystem::path& path);
// clear contents of desktop file
void clear();
// save desktop file
bool save() const;
// save desktop file to path
// does not change path associated with desktop file
// throws exceptions in case of errors, see DesktopFileWriter::save(...) for more information
bool save(const boost::filesystem::path& path) const;
// save desktop file to ostream
// does not change path associated with desktop file
// throws exceptions in case of errors, see DesktopFileWriter::save(...) for more information
bool save(std::ostream& os) const;
// check if entry exists in given section and key
bool entryExists(const std::string& section, const std::string& key) const;
// get key from desktop file
// an std::string passed as value parameter will be populated with the contents
// returns true (and populates value) if the key exists, false otherwise
bool getEntry(const std::string& section, const std::string& key, DesktopFileEntry& value) const;
// add key to section in desktop file
// the section will be created if it doesn't exist already
// returns true if an existing key was overwritten, false otherwise
bool setEntry(const std::string& section, const DesktopFileEntry& entry);
// add key to section in desktop file
// the section will be created if it doesn't exist already
// returns true if an existing key was overwritten, false otherwise
bool setEntry(const std::string& section, DesktopFileEntry&& entry);
// create common application entries in desktop file
// returns false if one of the keys exists and was left unmodified
bool addDefaultKeys(const std::string& executableFileName);
// validate desktop file
bool validate() const;
};
// DesktopFile equality operator
bool operator==(const DesktopFile& first, const DesktopFile& second);
// DesktopFile inequality operator
bool operator!=(const DesktopFile& first, const DesktopFile& second);
}
}
}

View File

@@ -1,71 +0,0 @@
#pragma once
// system headers
#include <memory>
#include <string>
// library headers
#include <boost/lexical_cast.hpp>
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileEntry {
private:
// opaque data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
public:
// default constructor
DesktopFileEntry();
// construct from key and value
explicit DesktopFileEntry(std::string key, std::string value);
// copy constructor
DesktopFileEntry(const DesktopFileEntry& other);
// copy assignment constructor
DesktopFileEntry& operator=(const DesktopFileEntry& other);
// move assignment operator
DesktopFileEntry& operator=(DesktopFileEntry&& other) noexcept;
// equality operator
bool operator==(const DesktopFileEntry& other) const;
// inequality operator
bool operator!=(const DesktopFileEntry& other) const;
public:
// checks whether a key and value have been set
bool isEmpty() const;
// return entry's key
const std::string& key() const;
// return entry's value
const std::string& value() const;
public:
// convert value to integer
// throws boost::bad_lexical_cast in case of type errors
int32_t asInt() const;
// convert value to long
// throws boost::bad_lexical_cast in case of type errors
int64_t asLong() const;
// convert value to double
// throws boost::bad_lexical_cast in case of type errors
double asDouble() const;
// split CSV list value into vector
// the separator used to split the string is a semicolon as per desktop file spec
std::vector<std::string> parseStringList() const;
};
}
}
}

View File

@@ -1,40 +0,0 @@
#pragma once
// system includes
#include <stdexcept>
#include <string>
namespace linuxdeploy {
namespace core {
namespace desktopfile {
/**
* Desktop file library's base exception.
*/
class DesktopFileError : public std::runtime_error {
public:
explicit DesktopFileError(const std::string& message = "unknown desktop file error") : runtime_error(message) {};
};
/**
* Exception thrown by DesktopFileReader on parsing errors.
*/
class ParseError : public DesktopFileError {
public:
explicit ParseError(const std::string& message = "unknown parse error") : DesktopFileError(message) {};
};
/**
* I/O exception, thrown if files cannot be opened, reading or writing fails etc.
*/
class IOError : public DesktopFileError {
public:
explicit IOError(const std::string& message = "unknown I/O error") : DesktopFileError(message) {};
};
class UnknownSectionError : public DesktopFileError {
public:
explicit UnknownSectionError(const std::string& section) : DesktopFileError("unknown section: " + section) {};
};
}
}
}

View File

@@ -9,6 +9,8 @@ add_library(args INTERFACE)
target_sources(args INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/args/args.hxx)
target_include_directories(args INTERFACE args)
add_subdirectory(linuxdeploy-desktopfile)
if(NOT USE_SYSTEM_BOOST)
add_library(boost_config INTERFACE)
set_property(TARGET boost_config PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-config/include>")
@@ -101,38 +103,8 @@ if(NOT USE_SYSTEM_BOOST)
boost_config boost_predef boost_assert boost_throw_exception boost_smart_ptr boost_core boost_mpl
boost_type_traits boost_static_assert boost_integer boost_preprocessor boost_functional boost_detail
)
add_library(boost_numeric_conversion INTERFACE)
set_property(TARGET boost_numeric_conversion PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-numeric_conversion/include>")
set_property(TARGET boost_numeric_conversion PROPERTY INTERFACE_LINK_LIBRARIES "boost_config")
add_library(boost_concept_check INTERFACE)
set_property(TARGET boost_concept_check PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-concept_check/include>")
set_property(TARGET boost_concept_check PROPERTY INTERFACE_LINK_LIBRARIES "boost_config")
target_link_libraries(boost_concept_check INTERFACE boost_numeric_conversion)
add_library(boost_array INTERFACE)
set_property(TARGET boost_array PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-array/include>")
set_property(TARGET boost_array PROPERTY INTERFACE_LINK_LIBRARIES "boost_config")
add_library(boost_move INTERFACE)
set_property(TARGET boost_move PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-move/include>")
set_property(TARGET boost_move PROPERTY INTERFACE_LINK_LIBRARIES "boost_config")
add_library(boost_container INTERFACE)
set_property(TARGET boost_container PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-container/include>")
set_property(TARGET boost_container PROPERTY INTERFACE_LINK_LIBRARIES boost_config boost_move)
add_library(boost_math INTERFACE)
set_property(TARGET boost_math PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-math/include>")
set_property(TARGET boost_math PROPERTY INTERFACE_LINK_LIBRARIES boost_config boost_move)
add_library(boost_lexical_cast INTERFACE)
set_property(TARGET boost_lexical_cast PROPERTY INTERFACE_INCLUDE_DIRECTORIES "$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/boost-lexical_cast/include>")
set_property(TARGET boost_lexical_cast PROPERTY INTERFACE_LINK_LIBRARIES "boost_config")
target_link_libraries(boost_lexical_cast INTERFACE boost_concept_check boost_numeric_conversion boost_array boost_container boost_math)
endif()
if(BUILD_TESTING)
if(BUILD_TESTING AND NOT TARGET gtest)
add_subdirectory(googletest)
endif()

Submodule lib/boost-array deleted from cef221d8b4

Submodule lib/boost-math deleted from 6bbba17f9e

Submodule lib/boost-move deleted from 3ce9452f93

View File

@@ -10,7 +10,7 @@ if(USE_SYSTEM_BOOST)
set(BOOST_LIBS Boost::filesystem Boost::regex)
else()
# use custom built libs
set(BOOST_LIBS boost_system boost_filesystem boost_regex boost_lexical_cast)
set(BOOST_LIBS boost_system boost_filesystem boost_regex)
endif()

View File

@@ -8,6 +8,7 @@
using namespace linuxdeploy::core;
using namespace linuxdeploy::core::log;
using namespace linuxdeploy::desktopfile;
namespace bf = boost::filesystem;
namespace linuxdeploy {

View File

@@ -18,11 +18,10 @@ target_include_directories(linuxdeploy_core_log PUBLIC ${PROJECT_SOURCE_DIR}/inc
target_link_libraries(linuxdeploy_core_log PUBLIC ${BOOST_LIBS})
add_subdirectory(copyright)
add_subdirectory(desktopfile)
add_library(linuxdeploy_core STATIC elf.cpp appdir.cpp ${HEADERS})
target_link_libraries(linuxdeploy_core PUBLIC
linuxdeploy_plugin linuxdeploy_core_log linuxdeploy_util linuxdeploy_core_desktopfile
linuxdeploy_plugin linuxdeploy_core_log linuxdeploy_util linuxdeploy_desktopfile
${BOOST_LIBS} CImg ${CMAKE_THREAD_LIBS_INIT}
)
target_link_libraries(linuxdeploy_core PRIVATE linuxdeploy_core_copyright)

View File

@@ -12,9 +12,9 @@
// local headers
#include "linuxdeploy/core/appdir.h"
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
#include "linuxdeploy/core/elf.h"
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/desktopfile/desktopfileentry.h"
#include "linuxdeploy/util/util.h"
#include "copyright.h"
@@ -22,7 +22,7 @@
#include "excludelist.h"
using namespace linuxdeploy::core;
using namespace linuxdeploy::core::desktopfile;
using namespace linuxdeploy::desktopfile;
using namespace linuxdeploy::core::log;
using namespace cimg_library;
@@ -651,7 +651,7 @@ namespace linuxdeploy {
}), paths.end());
for (const auto& path : paths) {
desktopFiles.emplace_back(path);
desktopFiles.emplace_back(path.string());
}
return desktopFiles;

View File

@@ -1,17 +0,0 @@
cmake_minimum_required(VERSION 3.0)
file(GLOB HEADERS ${PROJECT_SOURCE_DIR}/include/linuxdeploy/core/desktopfile/*.h)
add_library(linuxdeploy_core_desktopfile STATIC
desktopfile.cpp
desktopfileentry.cpp
desktopfilereader.cpp
desktopfilewriter.cpp
desktopfilereader.h
desktopfilewriter.h
${HEADERS}
)
target_include_directories(linuxdeploy_core_desktopfile PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(linuxdeploy_core_desktopfile PUBLIC linuxdeploy_util linuxdeploy_core_log ${BOOST_LIBS})

View File

@@ -1,214 +0,0 @@
// local headers
#include "linuxdeploy/core/desktopfile/exceptions.h"
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/core/log.h"
#include "desktopfilereader.h"
#include "desktopfilewriter.h"
using namespace linuxdeploy::core;
using namespace linuxdeploy::core::log;
namespace bf = boost::filesystem;
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFile::PrivateData {
public:
bf::path path;
sections_t data;
public:
PrivateData() = default;
void copyData(const std::shared_ptr<PrivateData>& other) {
path = other->path;
data = other->data;
}
public:
bool isEmpty() const {
return data.empty();
}
};
DesktopFile::DesktopFile() : d(std::make_shared<PrivateData>()) {}
DesktopFile::DesktopFile(const bf::path& path) : DesktopFile() {
// if the file doesn't exist, an exception shall be thrown
// otherwise, a user cannot know for sure whether a file was actually read (would need to check this
// manually beforehand
if (!bf::exists(path)) {
throw IOError("Could not find file " + path.string());
}
// will throw exceptions in case of issues
read(path);
};
DesktopFile::DesktopFile(std::istream& is) : DesktopFile() {
// will throw exceptions in case of issues
read(is);
};
// copy constructor
DesktopFile::DesktopFile(const DesktopFile& other) : DesktopFile() {
d->copyData(other.d);
}
// copy assignment constructor
DesktopFile& DesktopFile::operator=(const DesktopFile& other) {
if (this != &other) {
d->copyData(other.d);
}
return *this;
}
// move assignment operator
DesktopFile& DesktopFile::operator=(DesktopFile&& other) noexcept {
if (this != &other) {
d = other.d;
other.d = nullptr;
}
return *this;
}
void DesktopFile::read(const boost::filesystem::path& path) {
setPath(path);
// clear data before reading a new file
clear();
DesktopFileReader reader(path);
d->data = std::move(reader.data());
}
void DesktopFile::read(std::istream& is) {
// clear data before reading a new file
clear();
DesktopFileReader reader(is);
d->data = reader.data();
}
boost::filesystem::path DesktopFile::path() const {
return d->path;
}
void DesktopFile::setPath(const boost::filesystem::path& path) {
d->path = path;
}
bool DesktopFile::isEmpty() const {
return d->isEmpty();
}
void DesktopFile::clear() {
d->data.clear();
}
bool DesktopFile::save() const {
return save(d->path);
}
bool DesktopFile::save(const boost::filesystem::path& path) const {
DesktopFileWriter writer(d->data);
writer.save(path);
return true;
}
bool DesktopFile::save(std::ostream& os) const {
DesktopFileWriter writer(d->data);
writer.save(os);
return true;
}
bool DesktopFile::entryExists(const std::string& section, const std::string& key) const {
auto it = d->data.find(section);
if (it == d->data.end())
return false;
return (it->second.find(key) != it->second.end());
}
bool DesktopFile::setEntry(const std::string& section, const DesktopFileEntry& entry) {
// check if value exists -- used for return value
auto rv = entryExists(section, entry.key());
d->data[section][entry.key()] = entry;
return rv;
}
bool DesktopFile::setEntry(const std::string& section, DesktopFileEntry&& entry) {
// check if value exists -- used for return value
auto rv = entryExists(section, entry.key());
d->data[section][entry.key()] = entry;
return rv;
}
bool DesktopFile::getEntry(const std::string& section, const std::string& key, DesktopFileEntry& entry) const {
if (!entryExists(section, key))
return false;
entry = d->data[section][key];
// make sure keys are equal
assert(key == entry.key());
return true;
}
bool DesktopFile::addDefaultKeys(const std::string& executableFileName) {
ldLog() << "Adding default values to desktop file:" << path() << std::endl;
auto rv = true;
auto setDefault = [&rv, this](const std::string& section, const std::string& key, const std::string& value) {
if (entryExists(section, key)) {
DesktopFileEntry entry;
// this should never return false
auto entryExists = getEntry(section, key, entry);
assert(entryExists);
ldLog() << LD_WARNING << "Key exists, not modified:" << key << "(current value:" << entry.value() << LD_NO_SPACE << ")" << std::endl;
rv = false;
} else {
auto entryOverwritten = setEntry(section, DesktopFileEntry(key, value));
assert(!entryOverwritten);
}
};
setDefault("Desktop Entry", "Name", executableFileName);
setDefault("Desktop Entry", "Exec", executableFileName);
setDefault("Desktop Entry", "Icon", executableFileName);
setDefault("Desktop Entry", "Type", "Application");
setDefault("Desktop Entry", "Categories", "Utility;");
return rv;
}
bool DesktopFile::validate() const {
// FIXME: call desktop-file-validate
return true;
}
bool operator==(const DesktopFile& first, const DesktopFile& second) {
return first.d->path == second.d->path && first.d->data == second.d->data;
}
bool operator !=(const DesktopFile& first, const DesktopFile& second) {
return !operator==(first, second);
}
}
}
}

View File

@@ -1,125 +0,0 @@
// library headers
#include <boost/lexical_cast.hpp>
// local headers
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
using boost::lexical_cast;
namespace linuxdeploy {
namespace core {
using namespace log;
namespace desktopfile {
class DesktopFileEntry::PrivateData {
public:
std::string key;
std::string value;
public:
void copyData(const std::shared_ptr<PrivateData>& other) {
key = other->key;
value = other->value;
}
void assertValueNotEmpty() {
if (value.empty())
throw std::invalid_argument("value is empty");
}
};
DesktopFileEntry::DesktopFileEntry() : d(new PrivateData) {}
DesktopFileEntry::DesktopFileEntry(std::string key, std::string value) : DesktopFileEntry() {
d->key = std::move(key);
d->value = std::move(value);
}
DesktopFileEntry::DesktopFileEntry(const DesktopFileEntry& other) : DesktopFileEntry() {
d->copyData(other.d);
}
DesktopFileEntry& DesktopFileEntry::operator=(const DesktopFileEntry& other) {
if (this != &other) {
d.reset(new PrivateData);
d->copyData(other.d);
}
return *this;
}
DesktopFileEntry& DesktopFileEntry::operator=(DesktopFileEntry&& other) noexcept {
if (this != &other) {
d = other.d;
other.d = nullptr;
}
return *this;
}
bool DesktopFileEntry::operator==(const DesktopFileEntry& other) const {
return d->key == other.d->key && d->value == other.d->value;
}
bool DesktopFileEntry::operator!=(const DesktopFileEntry& other) const {
return !operator==(other);
}
bool DesktopFileEntry::isEmpty() const {
return d->key.empty();
}
const std::string& DesktopFileEntry::key() const {
return d->key;
}
const std::string& DesktopFileEntry::value() const {
return d->value;
}
int32_t DesktopFileEntry::asInt() const {
d->assertValueNotEmpty();
return lexical_cast<int32_t>(value());
}
int64_t DesktopFileEntry::asLong() const {
d->assertValueNotEmpty();
return lexical_cast<int64_t>(value());
}
double DesktopFileEntry::asDouble() const {
d->assertValueNotEmpty();
return lexical_cast<double>(value());
}
std::vector<std::string> DesktopFileEntry::parseStringList() const {
const auto& value = this->value();
if (value.empty())
return {};
if (value.back() != ';')
ldLog() << LD_DEBUG << "desktop file string list does not end with semicolon:" << value
<< std::endl;
std::vector<std::string> list;
std::stringstream ss(value);
std::string currentVal;
while (std::getline(ss, currentVal, ';')) {
// the last value will be empty, as in desktop files, lists shall end with a semicolon
// therefore we skip all empty values (assuming that empty values in lists in desktop files don't make sense anyway)
if (!currentVal.empty())
list.emplace_back(currentVal);
}
return list;
}
}
}
};

View File

@@ -1,216 +0,0 @@
// system includes
#include <fstream>
#include <sstream>
#include <unordered_map>
#include <utility>
// local headers
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
#include "desktopfilereader.h"
namespace bf = boost::filesystem;
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileReader::PrivateData {
public:
bf::path path;
DesktopFile::sections_t sections;
public:
bool isEmpty() {
return sections.empty();
}
void assertPathIsNotEmptyAndFileExists() {
if (path.empty())
throw IOError("empty path is not permitted");
}
void copyData(const std::shared_ptr<PrivateData>& other) {
path = other->path;
sections = other->sections;
}
void parse(std::istream& file) {
std::string line;
bool first = true;
std::string currentSectionName;
while (std::getline(file, line)) {
if (first) {
first = false;
// said to allow handling of UTF-16/32 documents, not entirely sure why
if (line[0] == static_cast<std::string::value_type>(0xEF)) {
line.erase(0, 3);
return;
}
}
if (!line.empty()) {
auto len = line.length();
if (len > 0 &&
!((len >= 2 && (line[0] == '/' && line[1] == '/')) || (len >= 1 && line[0] == '#'))) {
if (line[0] == '[') {
if (line.find_last_of('[') != 0)
throw ParseError("Multiple opening [ brackets");
// this line apparently introduces a new section
auto closingBracketPos = line.find(']');
auto lastClosingBracketPos = line.find_last_of(']');
if (closingBracketPos == std::string::npos)
throw ParseError("No closing ] bracket in section header");
else if (closingBracketPos != lastClosingBracketPos)
throw ParseError("Two or more closing ] brackets in section header");
size_t length = len - 2;
auto title = line.substr(1, closingBracketPos - 1);
// set up the new section
sections.insert(std::make_pair(title, DesktopFile::section_t()));
currentSectionName = std::move(title);
} else {
// we require at least one section to be present in the desktop file
if (currentSectionName.empty())
throw ParseError("No section in desktop file");
auto delimiterPos = line.find('=');
if (delimiterPos == std::string::npos)
throw ParseError("No = key/value delimiter found");
// this line should be a normal key-value pair
std::string key = line.substr(0, delimiterPos);
std::string value = line.substr(delimiterPos + 1, line.size());
// we can strip away any sort of leading or trailing whitespace safely
linuxdeploy::util::trim(key);
linuxdeploy::util::trim(value);
// empty keys are not allowed for obvious reasons
if (key.empty())
throw ParseError("Empty keys are not allowed");
// keys may only contain A-Za-z- characters according to specification
for (const char c : key) {
if (!(
(c >= 'A' && c <= 'Z') ||
(c >= 'a' && c <= 'z') ||
(c >= '0' && c <= '9') ||
(c == '-') ||
// FIXME: remove this hack after introducing localization support to
// conform to desktop file spec again
(c == '[') || (c == ']')
)
) {
throw ParseError(
"Key " + key + " contains invalid character " + std::string{c}
);
}
}
if (std::count(key.begin(), key.end(), '[') > 1 ||
std::count(key.begin(), key.end(), ']') > 1 ||
// make sure that both [ and ] are present
(key.find('[') != std::string::npos && key.find(']') == std::string::npos) ||
(key.find('[') == std::string::npos && key.find(']') != std::string::npos) ||
// disallow empty locale names
(key.find('[') != std::string::npos && key.find(']') != std::string::npos && (key.find(']') - key.find('[')) < 2) ||
// ensure order of [ and ]
(key.find('[') != std::string::npos && key.find('[' ) > key.find(']'))
) {
throw ParseError("Invalid localization syntax used in key " + key);
}
auto& section = sections[currentSectionName];
// keys must be unique in the same section
if (section.find(key) != section.end())
throw ParseError("Key " + key + " found more than once");
section[key] = DesktopFileEntry(key, value);
}
}
}
}
}
};
DesktopFileReader::DesktopFileReader() : d(new PrivateData) {}
DesktopFileReader::DesktopFileReader(boost::filesystem::path path) : DesktopFileReader() {
d->path = std::move(path);
d->assertPathIsNotEmptyAndFileExists();
std::ifstream ifs(d->path.string());
if (!ifs)
throw IOError("could not open file: " + d->path.string());
d->parse(ifs);
}
DesktopFileReader::DesktopFileReader(std::istream& is) : DesktopFileReader() {
d->parse(is);
}
DesktopFileReader::DesktopFileReader(const DesktopFileReader& other) : DesktopFileReader() {
d->copyData(other.d);
}
DesktopFileReader& DesktopFileReader::operator=(const DesktopFileReader& other) {
if (this != &other) {
// set up a new instance of PrivateData, and copy data over from other object
d.reset(new PrivateData);
d->copyData(other.d);
}
return *this;
}
DesktopFileReader& DesktopFileReader::operator=(DesktopFileReader&& other) noexcept {
if (this != &other) {
// move other object's data into this one, and remove reference there
d = other.d;
other.d = nullptr;
}
return *this;
}
bool DesktopFileReader::isEmpty() const {
return d->isEmpty();
}
bool DesktopFileReader::operator==(const DesktopFileReader& other) const {
return d->path == other.d->path && d->sections == other.d->sections;
}
bool DesktopFileReader::operator!=(const DesktopFileReader& other) const {
return !operator==(other);
}
boost::filesystem::path DesktopFileReader::path() const {
return d->path;
}
DesktopFile::sections_t DesktopFileReader::data() const {
return d->sections;
}
DesktopFile::section_t DesktopFileReader::operator[](const std::string& name) const {
auto it = d->sections.find(name);
// the map would lazy-initialize a new entry in case the section doesn't exist
// therefore explicitly checking whether the section exists, throwing an exception in case it does not
if (it == d->sections.end())
throw UnknownSectionError(name);
return it->second;
}
}
}
}

View File

@@ -1,66 +0,0 @@
#pragma once
// system includes
#include <istream>
#include <memory>
// library includes
#include <boost/filesystem.hpp>
// local includes
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileReader {
private:
// opaque data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
public:
// default constructor
DesktopFileReader();
// construct from path
explicit DesktopFileReader(boost::filesystem::path path);
// construct from existing istream
explicit DesktopFileReader(std::istream& is);
// copy constructor
DesktopFileReader(const DesktopFileReader& other);
// copy assignment constructor
DesktopFileReader& operator=(const DesktopFileReader& other);
// move assignment operator
DesktopFileReader& operator=(DesktopFileReader&& other) noexcept;
// equality operator
bool operator==(const DesktopFileReader& other) const;
// inequality operator
bool operator!=(const DesktopFileReader& other) const;
public:
// checks whether parsed data is available
bool isEmpty() const;
// returns desktop file path
boost::filesystem::path path() const;
// get a specific section from the parsed data
// throws std::range_error if section does not exist
DesktopFile::section_t operator[](const std::string& name) const;
// get copy of internal data storage
// can be handed to a DesktopFileWriter instance, or to manually hack on the data
DesktopFile::sections_t data() const;
};
}
}
}

View File

@@ -1,102 +0,0 @@
// system includes
#include <fstream>
#include <sstream>
// local headers
#include "linuxdeploy/util/util.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
#include "desktopfilewriter.h"
namespace bf = boost::filesystem;
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileWriter::PrivateData {
public:
DesktopFile::sections_t data;
public:
void copyData(const std::shared_ptr<PrivateData>& other) {
data = other->data;
}
std::string dumpString() {
std::stringstream ss;
for (const auto& section : data) {
ss << "[" << section.first << "]" << std::endl;
for (const auto& pair : section.second) {
auto key = pair.first;
util::trim(key);
auto value = pair.second.value();
util::trim(value);
ss << key << "=" << value << std::endl;
}
// insert an empty line between sections
ss << std::endl;
}
return ss.str();
}
};
DesktopFileWriter::DesktopFileWriter() : d(std::make_shared<PrivateData>()) {}
DesktopFileWriter::DesktopFileWriter(DesktopFile::sections_t data) : DesktopFileWriter() {
d->data = std::move(data);
}
DesktopFileWriter::DesktopFileWriter(const DesktopFileWriter& other) : DesktopFileWriter() {
d->copyData(other.d);
}
DesktopFileWriter& DesktopFileWriter::operator=(const DesktopFileWriter& other) {
if (this != &other) {
// set up a new instance of PrivateData, and copy data over from other object
d.reset(new PrivateData);
d->copyData(other.d);
}
return *this;
}
DesktopFileWriter& DesktopFileWriter::operator=(DesktopFileWriter&& other) noexcept {
if (this != &other) {
// move other object's data into this one, and remove reference there
d = other.d;
other.d = nullptr;
}
return *this;
}
bool DesktopFileWriter::operator==(const DesktopFileWriter& other) const {
return d->data == other.d->data;
}
bool DesktopFileWriter::operator!=(const DesktopFileWriter& other) const {
return !operator==(other);
}
DesktopFile::sections_t DesktopFileWriter::data() const {
return d->data;
}
void DesktopFileWriter::save(const boost::filesystem::path& path) {
std::ofstream ofs(path.string());
if (!ofs)
throw IOError("could not open file for writing: " + path.string());
save(ofs);
}
void DesktopFileWriter::save(std::ostream& os) {
os << d->dumpString();
}
}
}
}

View File

@@ -1,58 +0,0 @@
#pragma once
// system includes
#include <memory>
#include <ostream>
// library includes
#include <boost/filesystem.hpp>
// local includes
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
namespace linuxdeploy {
namespace core {
namespace desktopfile {
class DesktopFileWriter {
private:
// opaque data class pattern
class PrivateData;
std::shared_ptr<PrivateData> d;
public:
// default constructor
DesktopFileWriter();
// construct from data
explicit DesktopFileWriter(DesktopFile::sections_t data);
// copy constructor
DesktopFileWriter(const DesktopFileWriter& other);
// copy assignment constructor
DesktopFileWriter& operator=(const DesktopFileWriter& other);
// move assignment operator
DesktopFileWriter& operator=(DesktopFileWriter&& other) noexcept;
// equality operator
bool operator==(const DesktopFileWriter& other) const;
// inequality operator
bool operator!=(const DesktopFileWriter& other) const;
public:
// returns desktop file path
DesktopFile::sections_t data() const;
public:
// save to given path
void save(const boost::filesystem::path& path);
// save to given ostream
void save(std::ostream& os);
};
}
}
}

View File

@@ -7,13 +7,14 @@
// local headers
#include "linuxdeploy/core/appdir.h"
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/desktopfile/desktopfile.h"
#include "linuxdeploy/core/elf.h"
#include "linuxdeploy/core/log.h"
#include "linuxdeploy/plugin/plugin.h"
#include "linuxdeploy/util/util.h"
#include "core.h"
using namespace linuxdeploy;
using namespace linuxdeploy::core;
using namespace linuxdeploy::core::log;
using namespace linuxdeploy::util;
@@ -249,7 +250,7 @@ int main(int argc, char** argv) {
ldLog() << LD_WARNING << "Tried to overwrite existing entries in desktop file:" << desktopFilePath << std::endl;
}
if (!desktopFile.save(desktopFilePath)) {
if (!desktopFile.save(desktopFilePath.string())) {
ldLog() << LD_ERROR << "Failed to save desktop file:" << desktopFilePath << std::endl;
return 1;
}

View File

@@ -37,7 +37,3 @@ add_test(test_linuxdeploy test_linuxdeploy)
# make sure library and executable are built before test_appdir
add_dependencies(test_linuxdeploy simple_library simple_executable)
# include desktop file tests
add_subdirectory(desktopfile)

View File

@@ -1,17 +0,0 @@
# build a single test binary
add_executable(test_desktopfile
test_desktopfile.cpp
test_desktopfileentry.cpp
test_desktopfilereader.cpp
test_desktopfilewriter.cpp
test_desktopfile_conformance.cpp
main.cpp
)
target_link_libraries(test_desktopfile PRIVATE linuxdeploy_core_desktopfile gtest)
set_property(TARGET test_desktopfile
PROPERTY COMPILE_FLAGS "-DDESKTOP_FILE_PATH=\\\"${TEST_DATA_DIR}/simple_app.desktop\\\""
)
add_test(test_desktopfile test_desktopfile)

View File

@@ -1,15 +0,0 @@
// library headers
#include <gtest/gtest.h>
// local headers
#include <linuxdeploy/core/log.h>
using namespace linuxdeploy::core::log;
int main(int argc, char** argv) {
// set loglevel to prevent unnecessary log messages
ldLog::setVerbosity(LD_ERROR);
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}

View File

@@ -1,279 +0,0 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
// local headers
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
#include "../../src/core/desktopfile/desktopfilereader.h"
using boost::bad_lexical_cast;
using namespace linuxdeploy::core::desktopfile;
namespace bf = boost::filesystem;
class DesktopFileTest : public ::testing::Test {
public:
std::string testType;
std::string testName;
std::string testExec;
std::string testIcon;
std::string testDesktopFile;
private:
void SetUp() override {
testType = "Application";
testName = "Simple Application";
testExec = "simple_app";
testIcon = "simple_app";
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "Type=Application" << std::endl
<< "Name=Simple Application" << std::endl
<< "Exec=" << testExec << std::endl
<< "Icon=" << testIcon << std::endl;
testDesktopFile = ss.str();
}
void TearDown() override {}
public:
void assertIsTestDesktopFile(const DesktopFile& file, ssize_t expectedKeys = -1) {
std::stringstream ss;
file.save(ss);
assertHasTestDesktopFileKeys(ss, expectedKeys);
}
void assertHasTestDesktopFileKeys(std::stringstream& ss, ssize_t expectedKeys = -1) const {
DesktopFileReader reader(ss);
if (expectedKeys < 0)
expectedKeys = 4;
EXPECT_EQ(reader["Desktop Entry"]["Name"].value(), testName);
EXPECT_EQ(reader["Desktop Entry"]["Exec"].value(), testExec);
EXPECT_EQ(reader["Desktop Entry"]["Icon"].value(), testIcon);
EXPECT_EQ(reader["Desktop Entry"]["Type"].value(), testType);
EXPECT_EQ(reader["Desktop Entry"].size(), expectedKeys);
}
};
TEST_F(DesktopFileTest, testDefaultConstructor) {
DesktopFile file;
EXPECT_TRUE(file.isEmpty());
}
TEST_F(DesktopFileTest, testPathConstructor) {
ASSERT_THROW(DesktopFile nonExistingPath("/a/b/c/d/e/f/g/h/1/2/3/4/5/6/7/8"), IOError);
DesktopFile emptyFile("/dev/null");
EXPECT_TRUE(emptyFile.isEmpty());
DesktopFile file(DESKTOP_FILE_PATH);
EXPECT_FALSE(file.isEmpty());
}
TEST_F(DesktopFileTest, testStreamConstructor) {
std::stringstream emptyString;
DesktopFile emptyFile(emptyString);
EXPECT_TRUE(emptyFile.isEmpty());
std::stringstream ss;
ss << testDesktopFile;
DesktopFile file(ss);
EXPECT_FALSE(file.isEmpty());
assertIsTestDesktopFile(file);
}
TEST_F(DesktopFileTest, testCopyConstructor) {
DesktopFile empty;
EXPECT_TRUE(empty == empty);
EXPECT_FALSE(empty != empty);
DesktopFile copyOfEmpty(empty);
EXPECT_TRUE(empty == copyOfEmpty);
EXPECT_FALSE(empty != copyOfEmpty);
std::stringstream ss;
ss << testDesktopFile;
DesktopFile file(ss);
DesktopFile copy(file);
EXPECT_TRUE(file == copy);
EXPECT_FALSE(file != copy);
assertIsTestDesktopFile(file);
assertIsTestDesktopFile(copy);
}
TEST_F(DesktopFileTest, testCopyAssignmentConstructor) {
std::stringstream ss;
ss << testDesktopFile;
DesktopFile file(ss);
DesktopFile copy;
copy = file;
EXPECT_TRUE(file == copy);
EXPECT_FALSE(file != copy);
assertIsTestDesktopFile(file);
assertIsTestDesktopFile(copy);
}
TEST_F(DesktopFileTest, testMoveAssignmentConstructor) {
std::stringstream ss;
ss << testDesktopFile;
DesktopFile file(ss);
DesktopFile copy;
copy = std::move(file);
EXPECT_FALSE(copy.isEmpty());
assertIsTestDesktopFile(copy);
}
void assertDefaultKeysExistInDesktopFile(const DesktopFile& file) {
DesktopFileEntry entry;
for (const auto& key : {"Name", "Exec", "Icon", "Type"})
EXPECT_TRUE(file.getEntry("Desktop Entry", key, entry)) << "Could not find key in desktop file: " << key;
}
TEST_F(DesktopFileTest, testAddDefaultValues) {
const auto& value = "testExecutable";
DesktopFile file;
file.addDefaultKeys(value);
// make sure keys exist in desktop files
assertDefaultKeysExistInDesktopFile(file);
std::stringstream ss;
file.save(ss);
DesktopFileReader reader(ss);
EXPECT_EQ(reader["Desktop Entry"]["Name"].value(), value);
EXPECT_EQ(reader["Desktop Entry"]["Exec"].value(), value);
EXPECT_EQ(reader["Desktop Entry"]["Icon"].value(), value);
EXPECT_EQ(reader["Desktop Entry"]["Type"].value(), "Application");
EXPECT_EQ(reader["Desktop Entry"]["Categories"].value(), "Utility;");
}
TEST_F(DesktopFileTest, testAddDefaultValuesExistingKeys) {
const auto& value = "testExecutable";
std::stringstream iss;
iss << "[Desktop Entry]" << std::endl
<< "Name=A Different Name" << std::endl
<< "Exec=a_different_exec" << std::endl;
DesktopFile file(iss);
file.addDefaultKeys(value);
// make sure keys exist in desktop files
assertDefaultKeysExistInDesktopFile(file);
file.save(std::cout);
std::stringstream ss;
file.save(ss);
DesktopFileReader reader(ss);
EXPECT_EQ(reader["Desktop Entry"]["Name"].value(), "A Different Name");
EXPECT_EQ(reader["Desktop Entry"]["Exec"].value(), "a_different_exec");
EXPECT_EQ(reader["Desktop Entry"]["Icon"].value(), value);
EXPECT_EQ(reader["Desktop Entry"]["Categories"].value(), "Utility;");
}
TEST_F(DesktopFileTest, testAddDefaultValuesNoOverwrite) {
const auto& value = "testExecutable";
std::stringstream iss;
iss << testDesktopFile;
DesktopFile file(iss);
file.addDefaultKeys(value);
// make sure keys exist in desktop files
assertDefaultKeysExistInDesktopFile(file);
{
std::stringstream oss;
file.save(oss);
// keys should not have been overwritten, and should still have the original values
// however, there should be 5 keys, as the Categories one is coming from the testDesktopFile string
assertHasTestDesktopFileKeys(oss, 5);
}
{
std::stringstream oss;
file.save(oss);
DesktopFileReader reader(oss);
EXPECT_EQ(reader["Desktop Entry"]["Categories"].parseStringList(), std::vector<std::string>({"Utility"}));
}
}
TEST_F(DesktopFileTest, testSaveToPath) {
std::stringstream ins;
ins << testDesktopFile;
DesktopFile file(ins);
EXPECT_NO_THROW(file.save("/dev/null"));
}
TEST_F(DesktopFileTest, testSave) {
DesktopFile file("/dev/null");
EXPECT_NO_THROW(file.save());
}
TEST_F(DesktopFileTest, testSaveToStream) {
std::stringstream ins;
ins << testDesktopFile;
DesktopFile file(ins);
std::stringstream outs;
file.save(outs);
assertHasTestDesktopFileKeys(outs);
}
TEST_F(DesktopFileTest, testEquality) {
std::stringstream ins0(testDesktopFile);
std::stringstream ins1(testDesktopFile);
DesktopFile file0(ins0);
DesktopFile file1(ins1);
EXPECT_TRUE(file0 == file1);
EXPECT_FALSE(file0 != file1);
EXPECT_EQ(file0, file1);
}
TEST_F(DesktopFileTest, testInequality) {
std::stringstream ins;
ins << testDesktopFile;
DesktopFile file(ins);
DesktopFile emptyFile;
EXPECT_TRUE(file != emptyFile);
EXPECT_FALSE(file == emptyFile);
EXPECT_NE(file, emptyFile);
}

View File

@@ -1,90 +0,0 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
// local headers
#include "linuxdeploy/core/desktopfile/desktopfile.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
using boost::bad_lexical_cast;
using namespace linuxdeploy::core::desktopfile;
namespace bf = boost::filesystem;
class DesktopFileConformanceTest : public ::testing::Test {
private:
void SetUp() override {}
void TearDown() override {}
};
TEST_F(DesktopFileConformanceTest, testBasicFormatInvalidKeyCharacters) {
// test conformance with a couple of invalid values
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "no spaces in key=foo" << std::endl;
EXPECT_THROW(DesktopFile file(ss), ParseError);
}
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "UmlautTestÄöÜ=foo" << std::endl;
EXPECT_THROW(DesktopFile file(ss), ParseError);
}
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "NoUnderscores_=foo" << std::endl;
EXPECT_THROW(DesktopFile file(ss), ParseError);
}
}
TEST_F(DesktopFileConformanceTest, testBasicFormatValidKeyCharacters) {
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "TestKey=foo" << std::endl;
EXPECT_NO_THROW(DesktopFile file(ss));
}
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "4242trolol0=foo" << std::endl;
EXPECT_NO_THROW(DesktopFile file(ss));
}
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "----=foo" << std::endl;
EXPECT_NO_THROW(DesktopFile file(ss));
}
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "allLowerCase=foo" << std::endl;
EXPECT_NO_THROW(DesktopFile file(ss));
}
}
TEST_F(DesktopFileConformanceTest, testBasicFormatCheckKeysUnique) {
{
std::stringstream ss;
ss << "[Desktop Entry]" << std::endl
<< "foo=bar" << std::endl
<< "foo=baz" << std::endl;
EXPECT_THROW(DesktopFile file(ss), ParseError);
}
}

View File

@@ -1,146 +0,0 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
#include <boost/lexical_cast.hpp>
// local headers
#include "linuxdeploy/core/desktopfile/desktopfileentry.h"
using boost::bad_lexical_cast;
using namespace linuxdeploy::core::desktopfile;
namespace bf = boost::filesystem;
class DesktopFileEntryTest : public ::testing::Test {
public:
const std::string key;
const std::string value;
protected:
DesktopFileEntryTest() : key("testKey"), value("testValue") {}
private:
void SetUp() override {}
void TearDown() override {}
};
TEST_F(DesktopFileEntryTest, testDefaultConstructor) {
DesktopFileEntry entry;
EXPECT_TRUE(entry.isEmpty());
}
TEST_F(DesktopFileEntryTest, testKeyValueConstructor) {
DesktopFileEntry entry(key, value);
EXPECT_FALSE(entry.isEmpty());
EXPECT_EQ(entry.key(), key);
EXPECT_EQ(entry.value(), value);
}
TEST_F(DesktopFileEntryTest, testGetters) {
DesktopFileEntry entry(key, value);
EXPECT_EQ(entry.key(), key);
EXPECT_EQ(entry.value(), value);
}
TEST_F(DesktopFileEntryTest, testEqualityAndInequalityOperators) {
DesktopFileEntry emptyEntry;
EXPECT_TRUE(emptyEntry == emptyEntry);
EXPECT_FALSE(emptyEntry != emptyEntry);
DesktopFileEntry nonEmptyEntry(key, value);
EXPECT_NE(emptyEntry, nonEmptyEntry);
DesktopFileEntry nonEmptyEntryWithDifferentValue(key, value + "abc");
EXPECT_NE(nonEmptyEntry, nonEmptyEntryWithDifferentValue);
}
TEST_F(DesktopFileEntryTest, testCopyConstructor) {
DesktopFileEntry entry(key, value);
EXPECT_FALSE(entry.isEmpty());
DesktopFileEntry copy = entry;
EXPECT_FALSE(copy.isEmpty());
EXPECT_EQ(entry, copy);
}
TEST_F(DesktopFileEntryTest, testCopyAssignmentConstructor) {
DesktopFileEntry entry;
EXPECT_TRUE(entry.isEmpty());
DesktopFileEntry otherEntry(key, value);
EXPECT_FALSE(otherEntry.isEmpty());
entry = otherEntry;
EXPECT_EQ(entry.key(), key);
EXPECT_EQ(entry.value(), value);
// test self-assignment
entry = entry;
}
TEST_F(DesktopFileEntryTest, testMoveAssignmentConstructor) {
DesktopFileEntry entry;
EXPECT_TRUE(entry.isEmpty());
DesktopFileEntry otherEntry(key, value);
EXPECT_FALSE(otherEntry.isEmpty());
entry = std::move(otherEntry);
EXPECT_EQ(entry.key(), key);
EXPECT_EQ(entry.value(), value);
// test self-assignment
entry = std::move(entry);
}
TEST_F(DesktopFileEntryTest, testConversionToInt) {
DesktopFileEntry intEntry(key, "1234");
EXPECT_EQ(intEntry.asInt(), 1234);
DesktopFileEntry brokenValueEntry(key, "abcd");
ASSERT_THROW(brokenValueEntry.asInt(), bad_lexical_cast);
DesktopFileEntry emptyEntry(key, "");
ASSERT_THROW(emptyEntry.asInt(), std::invalid_argument);
}
TEST_F(DesktopFileEntryTest, testConversionToLong) {
DesktopFileEntry intEntry(key, "123456789123456789");
EXPECT_EQ(intEntry.asLong(), 123456789123456789L);
DesktopFileEntry brokenValueEntry(key, "abcd");
ASSERT_THROW(brokenValueEntry.asLong(), bad_lexical_cast);
DesktopFileEntry emptyEntry(key, "");
ASSERT_THROW(emptyEntry.asLong(), std::invalid_argument);
}
TEST_F(DesktopFileEntryTest, testConversionToDouble) {
DesktopFileEntry doubleEntry(key, "1.234567");
EXPECT_NEAR(doubleEntry.asDouble(), 1.234567, 0.00000001);
DesktopFileEntry brokenValueEntry(key, "abcd");
ASSERT_THROW(brokenValueEntry.asDouble(), bad_lexical_cast);
DesktopFileEntry emptyEntry(key, "");
ASSERT_THROW(emptyEntry.asDouble(), std::invalid_argument);
}
TEST_F(DesktopFileEntryTest, testParsingStringList) {
DesktopFileEntry emptyEntry(key, "");
EXPECT_EQ(emptyEntry.parseStringList(), std::vector<std::string>({}));
DesktopFileEntry nonListEntry(key, value);
EXPECT_EQ(nonListEntry.parseStringList(), std::vector<std::string>({value}));
DesktopFileEntry listEntry(key, "val1;val2;");
EXPECT_EQ(listEntry.parseStringList(), std::vector<std::string>({"val1", "val2"}));
DesktopFileEntry listEntryWithoutTrailingSemicolon(key, "val1;val2");
EXPECT_EQ(listEntry.parseStringList(), std::vector<std::string>({"val1", "val2"}));
DesktopFileEntry listEntryWithEmptyItems(key, "val1;;;val2;;");
EXPECT_EQ(listEntry.parseStringList(), std::vector<std::string>({"val1", "val2"}));
}

View File

@@ -1,468 +0,0 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
// local headers
#include "../../src/core/desktopfile/desktopfilereader.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
using namespace linuxdeploy::core::desktopfile;
namespace bf = boost::filesystem;
class DesktopFileReaderTest : public ::testing::Test {
void SetUp() override {}
void TearDown() override {}
};
TEST_F(DesktopFileReaderTest, testDefaultConstructor) {
DesktopFileReader reader;
EXPECT_TRUE(reader.isEmpty());
}
TEST_F(DesktopFileReaderTest, testPathConstructor) {
bf::path path = "/dev/null";
DesktopFileReader reader(path);
EXPECT_TRUE(reader.isEmpty());
ASSERT_THROW(DesktopFileReader("/no/such/file/or/directory"), IOError);
}
TEST_F(DesktopFileReaderTest, testStreamConstructor) {
std::stringstream ss;
DesktopFileReader reader(ss);
EXPECT_TRUE(reader.isEmpty());
}
TEST_F(DesktopFileReaderTest, testPathConstructorWithEmptyPath) {
ASSERT_THROW(DesktopFileReader(""), IOError);
}
TEST_F(DesktopFileReaderTest, testPathConstructorWithNonExistingPath) {
ASSERT_THROW(DesktopFileReader("/no/such/path/42"), IOError);
}
TEST_F(DesktopFileReaderTest, testEqualityAndInequalityOperators) {
{
DesktopFileReader emptyReader;
EXPECT_TRUE(emptyReader == emptyReader);
EXPECT_FALSE(emptyReader != emptyReader);
}
{
// make sure that files with different paths are recognized as different
DesktopFile nullFile("/dev/null");
DesktopFile fileWithoutPath;
EXPECT_TRUE(nullFile != fileWithoutPath);
EXPECT_FALSE(nullFile == fileWithoutPath);
}
{
// make sure that files with different contents are recognized as different
DesktopFile fileWithContents;
fileWithContents.setEntry("Desktop Entry", DesktopFileEntry("test", "test"));
DesktopFile emptyFile;
EXPECT_TRUE(emptyFile != fileWithContents);
EXPECT_FALSE(emptyFile == fileWithContents);
}
}
TEST_F(DesktopFileReaderTest, testCopyConstructor) {
{
bf::path path = "/dev/null";
DesktopFileReader reader(path);
EXPECT_TRUE(reader.isEmpty());
DesktopFileReader copy = reader;
EXPECT_TRUE(copy.isEmpty());
EXPECT_EQ(reader, copy);
}
{
// make sure that contents are copied, too
DesktopFile file;
file.setEntry("Desktop Entry", DesktopFileEntry("test", "test"));
std::stringstream ss;
file.save(ss);
DesktopFileReader reader(ss);
DesktopFileReader copy(reader);
EXPECT_TRUE(reader.data() == copy.data());
EXPECT_TRUE(reader == copy);
}
}
TEST_F(DesktopFileReaderTest, testCopyAssignmentConstructor) {
bf::path path = "/dev/null";
DesktopFileReader reader;
EXPECT_TRUE(reader.isEmpty());
DesktopFileReader otherReader(path);
EXPECT_TRUE(otherReader.isEmpty());
reader = otherReader;
EXPECT_EQ(reader.path(), path);
// test self-assignment
reader = reader;
}
TEST_F(DesktopFileReaderTest, testMoveAssignmentConstructor) {
bf::path path = "/dev/null";
DesktopFileReader reader;
EXPECT_TRUE(reader.isEmpty());
DesktopFileReader otherReader(path);
EXPECT_TRUE(otherReader.isEmpty());
reader = std::move(otherReader);
EXPECT_EQ(reader.path(), path);
// test self-assignment
reader = std::move(reader);
}
TEST_F(DesktopFileReaderTest, testParseSimpleDesktopFile) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Version=1.0" << std::endl
<< "Name=name" << std::endl
<< "Exec=exec" << std::endl
<< "Icon=icon" << std::endl
<< "Type=Application" << std::endl
<< "Categories=Utility;Multimedia;" << std::endl;
DesktopFileReader reader;
reader = DesktopFileReader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
EXPECT_EQ(section.size(), 6);
EXPECT_NEAR(section["Version"].asDouble(), 1.0, 0.000001);
EXPECT_EQ(section["Name"].value(), "name");
EXPECT_EQ(section["Exec"].value(), "exec");
EXPECT_EQ(section["Icon"].value(), "icon");
EXPECT_EQ(section["Type"].value(), "Application");
EXPECT_EQ(section["Name"].value(), "name");
EXPECT_EQ(section["Categories"].parseStringList(), std::vector<std::string>({"Utility", "Multimedia"}));
}
TEST_F(DesktopFileReaderTest, testParseSimpleDesktopFileWithComments) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Version=1.0" << std::endl
<< "Name=name" << std::endl
<< "# a comment" << std::endl
<< "Exec=exec" << std::endl
<< "Icon=icon" << std::endl
<< "Type=Application" << std::endl
<< "# another comment" << std::endl
<< "Categories=Utility;Multimedia;" << std::endl;
DesktopFileReader reader;
reader = DesktopFileReader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
EXPECT_EQ(section.size(), 6);
// TODO: check for comments in data
// right now, they're just discarded, but they shall be preserved in the right positions in the future
EXPECT_NEAR(section["Version"].asDouble(), 1.0f, 0.000001);
EXPECT_EQ(section["Name"].value(), "name");
EXPECT_EQ(section["Exec"].value(), "exec");
EXPECT_EQ(section["Icon"].value(), "icon");
EXPECT_EQ(section["Type"].value(), "Application");
EXPECT_EQ(section["Name"].value(), "name");
EXPECT_EQ(section["Categories"].parseStringList(), std::vector<std::string>({"Utility", "Multimedia"}));
}
TEST_F(DesktopFileReaderTest, testParseFileGetNonExistingSection) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl;
DesktopFileReader reader;
reader = DesktopFileReader(ss);
ASSERT_THROW(reader["Non-existing Section"], UnknownSectionError);
}
TEST_F(DesktopFileReaderTest, testParseFileMissingSectionHeader) {
std::stringstream ss;
ss << "Name=name" << std::endl
<< "Exec=exec" << std::endl
<< "Icon=icon" << std::endl
<< "Type=Application" << std::endl
<< "Categories=Utility;Multimedia;" << std::endl;
DesktopFileReader reader;
ASSERT_THROW(reader = DesktopFileReader(ss), ParseError);
}
TEST_F(DesktopFileReaderTest, testParseFileEmptyKey) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "=name" << std::endl
<< "Exec=exec" << std::endl
<< "Icon=icon" << std::endl
<< "Type=Application" << std::endl
<< "Categories=Utility;Multimedia;" << std::endl;
DesktopFileReader reader;
ASSERT_THROW(reader = DesktopFileReader(ss), ParseError);
}
TEST_F(DesktopFileReaderTest, testParseFileMissingDelimiterInLine) {
{
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Exec" << std::endl;
DesktopFileReader reader;
ASSERT_THROW(reader = DesktopFileReader(ss), ParseError);
}
{
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name name" << std::endl;
DesktopFileReader reader;
ASSERT_THROW(reader = DesktopFileReader(ss), ParseError);
}
}
TEST_F(DesktopFileReaderTest, testParseFile) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name name" << std::endl
<< "Exec" << std::endl;
DesktopFileReader reader;
ASSERT_THROW(reader = DesktopFileReader(ss), ParseError);
}
TEST_F(DesktopFileReaderTest, testParseFileMultipleDelimitersInLine) {
// TODO: verify that ==Name would be a legal value according to desktop file specification
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name===name" << std::endl;
DesktopFileReader reader;
ASSERT_NO_THROW(reader = DesktopFileReader(ss));
}
TEST_F(DesktopFileReaderTest, testParseFileWithLeadingAndTrailingWhitespaceInLines) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name= name" << std::endl
<< "Exec =exec" << std::endl;
DesktopFileReader reader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
EXPECT_EQ(section["Name"].value(), "name");
EXPECT_EQ(section["Exec"].value(), "exec");
}
TEST_F(DesktopFileReaderTest, testDataGetter) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name= name" << std::endl
<< "Exec =exec" << std::endl;
DesktopFileReader reader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
auto data = reader.data();
auto expected = DesktopFile::section_t({
{"Name", DesktopFileEntry("Name", "name")},
{"Exec", DesktopFileEntry("Exec", "exec")},
});
EXPECT_EQ(data["Desktop File"], expected);
}
TEST_F(DesktopFileReaderTest, testParseLinesWithMultipleSpaces) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name= What a great name " << std::endl;
DesktopFileReader reader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
EXPECT_EQ(section["Name"].value(), "What a great name");
}
TEST_F(DesktopFileReaderTest, testReadBrokenSectionHeaderMissingClosingBracket) {
{
std::stringstream ins;
ins << "[Desktop Entry" << std::endl
<< "test=test" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
// also test for brokenness in a later section, as the first section is normally treated specially
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test=test" << std::endl
<< "[Another Section" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
}
TEST_F(DesktopFileReaderTest, testReadBrokenSectionHeaderTooManyClosingBrackets) {
{
std::stringstream ins;
ins << "[Desktop Entry]]" << std::endl
<< "test=test" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
// also test for brokenness in a later section, as the first section is normally treated specially
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test=test" << std::endl
<< "[Another Section]]" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
}
TEST_F(DesktopFileReaderTest, testReadBrokenSectionHeaderTooManyOpeningBrackets) {
{
std::stringstream ins;
ins << "[[Desktop Entry]" << std::endl
<< "test=test" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
// also test for brokenness in a later section, as the first section is normally treated specially
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test=test" << std::endl
<< "[[Another Section]";
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
}
TEST_F(DesktopFileReaderTest, testReadBrokenSectionMissingOpeningBracket) {
{
std::stringstream ins;
ins << "Desktop Entry]" << std::endl
<< "test=test" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
// also test for brokenness in a later section, as the first section is normally treated specially
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test=test" << std::endl
<< "Another Section]" << std::endl;
ASSERT_THROW(DesktopFileReader reader(ins), ParseError);
}
}
// FIXME: introduce proper localization support
TEST_F(DesktopFileReaderTest, testReadLocalizedEntriesWithoutProperLocalizationSupport) {
std::stringstream ss;
ss << "[Desktop File]" << std::endl
<< "Name=name" << std::endl
<< "Name[de]=name" << std::endl
<< "Exec=exec" << std::endl;
DesktopFileReader reader(ss);
auto section = reader["Desktop File"];
EXPECT_FALSE(section.empty());
auto data = reader.data();
auto expected = DesktopFile::section_t({
{"Name", DesktopFileEntry("Name", "name")},
// FIXME: revise after introduction of localization support
{"Name[de]", DesktopFileEntry("Name[de]", "name")},
{"Exec", DesktopFileEntry("Exec", "exec")},
});
EXPECT_EQ(data["Desktop File"], expected);
}
TEST_F(DesktopFileReaderTest, testBrokenLocalizedKeys) {
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test]de[=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test[de]]=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test[[de]=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test[]de=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "test[de=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
{
std::stringstream ins;
ins << "[Desktop Entry]" << std::endl
<< "testde]=test" << std::endl;
EXPECT_THROW(DesktopFileReader reader(ins), ParseError);
}
}

View File

@@ -1,108 +0,0 @@
// library headers
#include <gtest/gtest.h>
#include <boost/filesystem.hpp>
// local headers
#include "../../src/core/desktopfile/desktopfilewriter.h"
#include "../../src/core/desktopfile/desktopfilereader.h"
#include "linuxdeploy/core/desktopfile/exceptions.h"
using namespace linuxdeploy::core::desktopfile;
namespace bf = boost::filesystem;
class DesktopFileWriterTest : public ::testing::Test {
void SetUp() override {}
void TearDown() override {}
};
TEST_F(DesktopFileWriterTest, testDefaultConstructor) {
DesktopFileWriter writer;
}
TEST_F(DesktopFileWriterTest, testDataConstructor) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
}
TEST_F(DesktopFileWriterTest, testCopyConstructor) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
DesktopFileWriter copy(writer);
EXPECT_EQ(copy, writer);
}
TEST_F(DesktopFileWriterTest, testCopyAssignmentConstructor) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
DesktopFileWriter otherWriter;
otherWriter = writer;
EXPECT_EQ(writer, otherWriter);
}
TEST_F(DesktopFileWriterTest, testEqualityAndInequalityOperators) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
DesktopFileWriter otherWriter(data);
EXPECT_TRUE(writer == otherWriter);
EXPECT_FALSE(writer != otherWriter);
}
TEST_F(DesktopFileWriterTest, testMoveAssignmentConstructor) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
DesktopFileWriter otherWriter;
otherWriter = std::move(writer);
}
TEST_F(DesktopFileWriterTest, testSaveToPath) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
writer.save("/dev/null");
}
TEST_F(DesktopFileWriterTest, testSaveToInvalidPath) {
DesktopFileWriter writer;
ASSERT_THROW(writer.save("/a/b/c/d/e/f/g/h/1/2/3/4/5/6/7/8"), IOError);
}
TEST_F(DesktopFileWriterTest, testSaveToStream) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
std::stringstream ss;
writer.save(ss);
}
TEST_F(DesktopFileWriterTest, testDataGetter) {
DesktopFile::sections_t data;
DesktopFileWriter writer(data);
EXPECT_EQ(data, writer.data());
}
TEST_F(DesktopFileWriterTest, testSerialization) {
DesktopFile::section_t section = {
{"Exec", DesktopFileEntry("Exec", "exec")},
{"Name", DesktopFileEntry("Name", "name")},
};
DesktopFile::sections_t data = {
{"Desktop File", section},
};
DesktopFileWriter writer(data);
EXPECT_EQ(data, writer.data());
std::stringstream ss;
writer.save(ss);
DesktopFileReader reader(ss);
EXPECT_EQ(reader["Desktop File"], section);
}

View File

@@ -2,7 +2,7 @@
#include "linuxdeploy/core/appdir.h"
using namespace linuxdeploy::core::appdir;
using namespace linuxdeploy::core::desktopfile;
using namespace linuxdeploy::desktopfile;
using namespace boost::filesystem;
namespace AppDirTest {