"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/interface/Options.cpp" between
FileZilla_3.48.0_src.tar.bz2 and FileZilla_3.48.1_src.tar.bz2

About: FileZilla is a fast and feature-rich cross-platform FTP, FTPS and SFTP client with an intuitive graphical user interface.

Options.cpp  (FileZilla_3.48.0_src.tar.bz2):Options.cpp  (FileZilla_3.48.1_src.tar.bz2)
#include <filezilla.h> #include <filezilla.h>
#include "Options.h" #include "Options.h"
#include "filezillaapp.h" #include "filezillaapp.h"
#include "file_utils.h" #include "file_utils.h"
#include "ipcmutex.h" #include "ipcmutex.h"
#include "locale_initializer.h" #include "locale_initializer.h"
#include <option_change_event_handler.h> #include <option_change_event_handler.h>
#include "sizeformatting.h" #include "sizeformatting.h"
#include <libfilezilla/local_filesys.hpp>
#include <algorithm> #include <algorithm>
#include <string> #include <string>
#include <stdlib.h> #include <stdlib.h>
#ifdef FZ_WINDOWS #ifdef FZ_WINDOWS
#include <shlobj.h> #include <shlobj.h>
// Needed for MinGW: // Needed for MinGW:
#ifndef SHGFP_TYPE_CURRENT #ifndef SHGFP_TYPE_CURRENT
skipping to change at line 169 skipping to change at line 171
{ "Window position and size", string, L"", normal }, { "Window position and size", string, L"", normal },
{ "Splitter positions (v2)", string, L"", normal }, { "Splitter positions (v2)", string, L"", normal },
{ "Local filelist sortorder", string, L"", normal }, { "Local filelist sortorder", string, L"", normal },
{ "Remote filelist sortorder", string, L"", normal }, { "Remote filelist sortorder", string, L"", normal },
{ "Time Format", string, L"", normal }, { "Time Format", string, L"", normal },
{ "Date Format", string, L"", normal }, { "Date Format", string, L"", normal },
{ "Show message log", number, L"1", normal }, { "Show message log", number, L"1", normal },
{ "Show queue", number, L"1", normal }, { "Show queue", number, L"1", normal },
{ "Default editor", string, L"", platform }, { "Default editor", string, L"", platform },
{ "Always use default editor", number, L"0", normal }, { "Always use default editor", number, L"0", normal },
{ "File associations", string, L"", platform }, { "File associations (v2)", string, L"", platform },
{ "Comparison mode", number, L"1", normal }, { "Comparison mode", number, L"1", normal },
{ "Comparison threshold", number, L"1", normal }, { "Comparison threshold", number, L"1", normal },
{ "Site Manager position", string, L"", normal }, { "Site Manager position", string, L"", normal },
{ "Icon theme", string, L"default", normal }, { "Icon theme", string, L"default", normal },
{ "Icon scale", number, L"125", normal }, { "Icon scale", number, L"125", normal },
{ "Timestamp in message log", number, L"0", normal }, { "Timestamp in message log", number, L"0", normal },
{ "Sitemanager last selected", string, L"", normal }, { "Sitemanager last selected", string, L"", normal },
{ "Local filelist shown columns", string, L"", normal }, { "Local filelist shown columns", string, L"", normal },
{ "Remote filelist shown columns", string, L"", normal }, { "Remote filelist shown columns", string, L"", normal },
{ "Local filelist column order", string, L"", normal }, { "Local filelist column order", string, L"", normal },
skipping to change at line 258 skipping to change at line 260
return *this; return *this;
} }
t_OptionsCache& t_OptionsCache::operator=(int v) t_OptionsCache& t_OptionsCache::operator=(int v)
{ {
numValue = v; numValue = v;
strValue = fz::to_wstring(v); strValue = fz::to_wstring(v);
return *this; return *this;
} }
t_OptionsCache& t_OptionsCache::operator=(std::unique_ptr<pugi::xml_document> co nst& v) t_OptionsCache& t_OptionsCache::operator=(pugi::xml_document const& v)
{ {
xmlValue = std::make_unique<pugi::xml_document>(); xmlValue.reset();
for (auto c = v->first_child(); c; c = c.next_sibling()) { for (auto c = v.first_child(); c; c = c.next_sibling()) {
xmlValue->append_copy(c); xmlValue.append_copy(c);
} }
return *this; return *this;
} }
t_OptionsCache& t_OptionsCache::operator=(pugi::xml_document && v)
{
xmlValue = std::move(v);
return *this;
}
COptions::COptions() COptions::COptions()
{ {
m_theOptions = this; m_theOptions = this;
SetDefaultValues(); SetDefaultValues();
m_save_timer.SetOwner(this); m_save_timer.SetOwner(this);
auto const nameOptionMap = GetNameOptionMap(); auto const nameOptionMap = GetNameOptionMap();
LoadGlobalDefaultOptions(nameOptionMap); LoadGlobalDefaultOptions(nameOptionMap);
skipping to change at line 293 skipping to change at line 301
if (!xmlFile_->Load()) { if (!xmlFile_->Load()) {
wxString msg = xmlFile_->GetError() + L"\n\n" + _("For this sessi on the default settings will be used. Any changes to the settings will not be sa ved."); wxString msg = xmlFile_->GetError() + L"\n\n" + _("For this sessi on the default settings will be used. Any changes to the settings will not be sa ved.");
wxMessageBoxEx(msg, _("Error loading xml file"), wxICON_ERROR); wxMessageBoxEx(msg, _("Error loading xml file"), wxICON_ERROR);
xmlFile_.reset(); xmlFile_.reset();
} }
else { else {
CreateSettingsXmlElement(); CreateSettingsXmlElement();
} }
LoadOptions(nameOptionMap); LoadOptions(nameOptionMap);
changedOptions_.reset();
} }
std::map<std::string, unsigned int> COptions::GetNameOptionMap() const std::map<std::string, unsigned int> COptions::GetNameOptionMap() const
{ {
std::map<std::string, unsigned int> ret; std::map<std::string, unsigned int> ret;
for (unsigned int i = 0; i < OPTIONS_NUM; ++i) { for (unsigned int i = 0; i < OPTIONS_NUM; ++i) {
if (!(options[i].flags & internal)) { if (!(options[i].flags & internal)) {
ret.insert(std::make_pair(std::string(options[i].name), i )); ret.insert(std::make_pair(std::string(options[i].name), i ));
} }
} }
skipping to change at line 331 skipping to change at line 341
std::wstring COptions::GetOption(unsigned int nID) std::wstring COptions::GetOption(unsigned int nID)
{ {
if (nID >= OPTIONS_NUM) { if (nID >= OPTIONS_NUM) {
return std::wstring(); return std::wstring();
} }
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
return m_optionsCache[nID].strValue; return m_optionsCache[nID].strValue;
} }
std::unique_ptr<pugi::xml_document> COptions::GetOptionXml(unsigned int nID) pugi::xml_document COptions::GetOptionXml(unsigned int nID)
{ {
if (nID >= OPTIONS_NUM) { pugi::xml_document ret;
return nullptr; if (nID < OPTIONS_NUM) {
} fz::scoped_lock l(m_sync_);
for (auto c = m_optionsCache[nID].xmlValue.first_child(); c; c =
auto value = std::make_unique<pugi::xml_document>(); c.next_sibling()) {
for (auto c = m_optionsCache[nID].xmlValue->first_child(); c; c = c.next_ ret.append_copy(c);
sibling()) { }
value->append_copy(c);
} }
return value; return ret;
} }
bool COptions::SetOption(unsigned int nID, int value) bool COptions::SetOption(unsigned int nID, int value)
{ {
if (nID >= OPTIONS_NUM) { if (nID >= OPTIONS_NUM) {
return false; return false;
} }
if (options[nID].type != number) { if (options[nID].type != number) {
return false; return false;
skipping to change at line 383 skipping to change at line 392
bool COptions::SetOptionXml(unsigned int nID, pugi::xml_node const& value) bool COptions::SetOptionXml(unsigned int nID, pugi::xml_node const& value)
{ {
if (nID >= OPTIONS_NUM) { if (nID >= OPTIONS_NUM) {
return false; return false;
} }
if (options[nID].type != xml) { if (options[nID].type != xml) {
return false; return false;
} }
auto doc = std::make_unique<pugi::xml_document>(); pugi::xml_document doc;
if (value) { if (value) {
if (value.type() == pugi::node_document) { if (value.type() == pugi::node_document) {
for (auto c = value.first_child(); c; c = c.next_sibling( )) { for (auto c = value.first_child(); c; c = c.next_sibling( )) {
if (c.type() == pugi::node_element) { if (c.type() == pugi::node_element) {
doc->append_copy(c); doc.append_copy(c);
} }
} }
} }
else { else {
doc->append_copy(value); doc.append_copy(value);
} }
} }
ContinueSetOption(nID, doc); ContinueSetOption(nID, doc);
return true; return true;
} }
template<typename T> template<typename T>
void COptions::ContinueSetOption(unsigned int nID, T const& value) void COptions::ContinueSetOption(unsigned int nID, T const& value)
{ {
auto validated = Validate(nID, value); auto validated = Validate(nID, value);
{ {
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
if (m_optionsCache[nID] == validated) { if (m_optionsCache[nID] == validated) {
// Nothing to do // Nothing to do
return; return;
} }
m_optionsCache[nID] = validated; m_optionsCache[nID] = validated;
if (changedOptions_.none()) {
CallAfter(&COptions::NotifyChangedOptions);
}
changedOptions_.set(nID);
} }
// Fixme: Setting options from other threads // Fixme: Setting options from other threads
if (!wxIsMainThread()) { if (!wxIsMainThread()) {
return; return;
} }
if (!xmlFile_) {
return;
}
auto settings = CreateSettingsXmlElement();
if (!settings) {
return;
}
if (!(options[nID].flags & (internal | default_only))) { if (!(options[nID].flags & (internal | default_only))) {
SetXmlValue(nID, validated); SetXmlValue(nID, settings, validated);
if (!m_save_timer.IsRunning()) { if (!m_save_timer.IsRunning()) {
m_save_timer.Start(15000, true); m_save_timer.Start(15000, true);
} }
} }
if (changedOptions_.none()) {
CallAfter(&COptions::NotifyChangedOptions);
}
changedOptions_.set(nID);
} }
void COptions::NotifyChangedOptions() void COptions::NotifyChangedOptions()
{ {
// Reset prior to notifying to correctly handle the case of an option bei ng set while notifying // Reset prior to notifying to correctly handle the case of an option bei ng set while notifying
auto changedOptions = changedOptions_; changed_options_t changedOptions;
changedOptions_.reset(); {
fz::scoped_lock l(m_sync_);
std::swap(changedOptions, changedOptions_);
}
COptionChangeEventHandler::DoNotify(changedOptions); COptionChangeEventHandler::DoNotify(changedOptions);
} }
bool COptions::OptionFromFzDefaultsXml(unsigned int nID) bool COptions::OptionFromFzDefaultsXml(unsigned int nID)
{ {
if (nID >= OPTIONS_NUM) { if (nID >= OPTIONS_NUM) {
return false; return false;
} }
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
skipping to change at line 470 skipping to change at line 490
if (!element) { if (!element) {
return element; return element;
} }
auto settings = element.child("Settings"); auto settings = element.child("Settings");
if (settings) { if (settings) {
return settings; return settings;
} }
settings = element.append_child("Settings"); settings = element.append_child("Settings");
WriteCacheToXml(settings);
return settings;
}
void COptions::WriteCacheToXml(pugi::xml_node settings)
{
fz::scoped_lock l(m_sync_);
for (int i = 0; i < OPTIONS_NUM; ++i) { for (int i = 0; i < OPTIONS_NUM; ++i) {
if ((options[i].flags & (internal | default_only))) {
continue;
}
if (options[i].type == string) { if (options[i].type == string) {
SetXmlValue(i, GetOption(i)); SetXmlValue(i, settings, m_optionsCache[i].strValue);
} }
else if (options[i].type == xml) { else if (options[i].type == xml) {
SetXmlValue(i, GetOptionXml(i)); SetXmlValue(i, settings, m_optionsCache[i].xmlValue);
} }
else { else {
SetXmlValue(i, GetOptionVal(i)); SetXmlValue(i, settings, m_optionsCache[i].numValue);
} }
} }
return settings;
} }
void COptions::SetXmlValue(unsigned int nID, int value) void COptions::SetXmlValue(unsigned int nID, pugi::xml_node settings, int value)
{ {
SetXmlValue(nID, fz::to_wstring(value)); SetXmlValue(nID, settings, fz::to_wstring(value));
} }
void COptions::SetXmlValue(unsigned int nID, std::wstring_view const& value) void COptions::SetXmlValue(unsigned int nID, pugi::xml_node settings, std::wstri ng_view const& value)
{ {
if (!xmlFile_) {
return;
}
// No checks are made about the validity of the value, that's done in Set Option // No checks are made about the validity of the value, that's done in Set Option
std::string utf8 = fz::to_utf8(value); std::string utf8 = fz::to_utf8(value);
auto settings = CreateSettingsXmlElement(); for (pugi::xml_node it = settings.child("Setting"); it;) {
if (settings) { auto cur = it;
pugi::xml_node setting; it = it.next_sibling("Setting");
for (pugi::xml_node it = settings.child("Setting"); it;) {
auto cur = it; char const *attribute = cur.attribute("name").value();
it = it.next_sibling("Setting"); if (strcmp(attribute, options[nID].name)) {
continue;
}
char const *attribute = cur.attribute("name").value(); if (options[nID].flags & platform) {
if (strcmp(attribute, options[nID].name)) { // Ignore items from the wrong platform
char const* p = cur.attribute("platform").value();
if (*p && strcmp(p, platform_name)) {
continue; continue;
} }
if (options[nID].flags & platform) {
// Ignore items from the wrong platform
char const* p = cur.attribute("platform").value()
;
if (*p && strcmp(p, platform_name)) {
continue;
}
}
if (setting) {
// Remove duplicates
settings.remove_child(cur);
}
else {
setting = cur;
}
break;
}
if (!setting) {
setting = settings.append_child("Setting");
SetTextAttribute(setting, "name", options[nID].name);
}
if (options[nID].flags & platform) {
SetTextAttribute(setting, "platform", platform_name);
} }
setting.text() = utf8.c_str(); settings.remove_child(cur);
}
pugi::xml_node setting = settings.append_child("Setting");
SetTextAttribute(setting, "name", options[nID].name);
if (options[nID].flags & platform) {
SetTextAttribute(setting, "platform", platform_name);
} }
setting.text() = utf8.c_str();
} }
void COptions::SetXmlValue(unsigned int nID, std::unique_ptr<pugi::xml_document> const& value) void COptions::SetXmlValue(unsigned int nID, pugi::xml_node settings, pugi::xml_ document const& value)
{ {
if (!xmlFile_) { for (pugi::xml_node it = settings.child("Setting"); it;) {
return; auto cur = it;
} it = it.next_sibling("Setting");
auto settings = CreateSettingsXmlElement();
if (settings) {
pugi::xml_node setting;
for (pugi::xml_node it = settings.child("Setting"); it;) {
auto cur = it;
it = it.next_sibling("Setting");
const char *attribute = cur.attribute("name").value(); const char *attribute = cur.attribute("name").value();
if (strcmp(attribute, options[nID].name)) { if (strcmp(attribute, options[nID].name)) {
continue; continue;
}
if (options[nID].flags & platform) {
// Ignore items from the wrong platform
char const* p = cur.attribute("platform").value()
;
if (*p && strcmp(p, platform_name)) {
continue;
}
}
break;
}
if (setting) {
settings.remove_child(setting);
} }
if (value && value->first_child()) { if (options[nID].flags & platform) {
setting = settings.append_child("Setting"); // Ignore items from the wrong platform
SetTextAttribute(setting, "name", options[nID].name); char const* p = cur.attribute("platform").value();
if (options[nID].flags & platform) { if (*p && strcmp(p, platform_name)) {
SetTextAttribute(setting, "platform", platform_na continue;
me);
}
for (auto c = value->first_child(); c; c = c.next_sibling
()) {
setting.append_copy(c);
} }
} }
settings.remove_child(cur);
}
pugi::xml_node setting = settings.append_child("Setting");
SetTextAttribute(setting, "name", options[nID].name);
if (options[nID].flags & platform) {
SetTextAttribute(setting, "platform", platform_name);
}
for (auto c = value.first_child(); c; c = c.next_sibling()) {
setting.append_copy(c);
} }
} }
int COptions::Validate(unsigned int nID, int value) int COptions::Validate(unsigned int nID, int value)
{ {
switch (nID) switch (nID)
{ {
case OPTION_UPDATECHECK_INTERVAL: case OPTION_UPDATECHECK_INTERVAL:
if (value < 1 || value > 7) { if (value < 1 || value > 7) {
value = 7; value = 7;
skipping to change at line 713 skipping to change at line 711
if (value.size() != 1) { if (value.size() != 1) {
return L"_"; return L"_";
} }
if (IsInvalidChar(value[0], true)) { if (IsInvalidChar(value[0], true)) {
return L"_"; return L"_";
} }
} }
return std::wstring(value); return std::wstring(value);
} }
std::unique_ptr<pugi::xml_document> COptions::Validate(unsigned int, std::unique _ptr<pugi::xml_document> const& value) pugi::xml_document COptions::Validate(unsigned int, pugi::xml_document const& va lue)
{ {
auto res = std::make_unique<pugi::xml_document>(); pugi::xml_document res;
for (auto c = value->first_child(); c; c = c.next_sibling()) { for (auto c = value.first_child(); c; c = c.next_sibling()) {
if (c.type() == pugi::node_element) { if (c.type() == pugi::node_element) {
res->append_copy(c); res.append_copy(c);
} }
} }
return res; return res;
} }
void COptions::Init() void COptions::Init()
{ {
if (!m_theOptions) { if (!m_theOptions) {
new COptions(); // It sets m_theOptions internally itself new COptions(); // It sets m_theOptions internally itself
skipping to change at line 749 skipping to change at line 747
m_theOptions = 0; m_theOptions = 0;
} }
COptions* COptions::Get() COptions* COptions::Get()
{ {
return m_theOptions; return m_theOptions;
} }
void COptions::Import(pugi::xml_node element) void COptions::Import(pugi::xml_node element)
{ {
bool canNotifyChanged{};
{
fz::scoped_lock l(m_sync_);
canNotifyChanged = changedOptions_.none();
}
LoadOptions(GetNameOptionMap(), element); LoadOptions(GetNameOptionMap(), element);
if (!m_save_timer.IsRunning()) { if (!m_save_timer.IsRunning()) {
m_save_timer.Start(15000, true); m_save_timer.Start(15000, true);
} }
}
void COptions::LoadOptions(std::map<std::string, unsigned int> const& nameOption {
Map, pugi::xml_node settings) fz::scoped_lock l(m_sync_);
{ if (canNotifyChanged && changedOptions_.any()) {
if (!settings) { CallAfter(&COptions::NotifyChangedOptions);
settings = CreateSettingsXmlElement();
if (!settings) {
return;
} }
} }
}
void COptions::LoadOptions(std::map<std::string, unsigned int> const& nameOption
Map, pugi::xml_node import)
{
pugi::xml_node settings = CreateSettingsXmlElement();
for (auto setting = settings.child("Setting"); setting; setting = setting pugi::xml_node first = import ? import.child("Setting") : settings.child(
.next_sibling("Setting")) { "Setting");
for (auto setting = first; setting; setting = setting.next_sibling("Setti
ng")) {
LoadOptionFromElement(setting, nameOptionMap, false); LoadOptionFromElement(setting, nameOptionMap, false);
} }
if (import) {
WriteCacheToXml(settings);
}
} }
void COptions::LoadOptionFromElement(pugi::xml_node option, std::map<std::string , unsigned int> const& nameOptionMap, bool allowDefault) void COptions::LoadOptionFromElement(pugi::xml_node option, std::map<std::string , unsigned int> const& nameOptionMap, bool allowDefault)
{ {
const char* name = option.attribute("name").value(); const char* name = option.attribute("name").value();
if (!name) { if (!name) {
return; return;
} }
auto const iter = nameOptionMap.find(name); auto const iter = nameOptionMap.find(name);
skipping to change at line 807 skipping to change at line 818
if (m_optionsCache[iter->second].from_default) { if (m_optionsCache[iter->second].from_default) {
return; return;
} }
} }
} }
if (options[iter->second].type == number) { if (options[iter->second].type == number) {
int numValue = fz::to_integral<int>(value); int numValue = fz::to_integral<int>(value);
numValue = Validate(iter->second, numValue); numValue = Validate(iter->second, numValue);
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
m_optionsCache[iter->second] = numValue; if (m_optionsCache[iter->second].numValue != numValue) {
m_optionsCache[iter->second] = numValue;
changedOptions_.set(iter->second);
}
} }
else if (options[iter->second].type == string) { else if (options[iter->second].type == string) {
value = Validate(iter->second, value); value = Validate(iter->second, value);
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
m_optionsCache[iter->second] = value; if (m_optionsCache[iter->second].strValue != value) {
m_optionsCache[iter->second] = value;
changedOptions_.set(iter->second);
}
} }
else { else {
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
if (!option.first_child().empty()) { m_optionsCache[iter->second].xmlValue.reset();
m_optionsCache[iter->second].xmlValue = std::make for (auto c = option.first_child(); c; c = c.next_sibling
_unique<pugi::xml_document>(); ()) {
for (auto c = option.first_child(); c; c = c.next m_optionsCache[iter->second].xmlValue.append_copy
_sibling()) { (c);
m_optionsCache[iter->second].xmlValue->ap
pend_copy(c);
}
} }
changedOptions_.set(iter->second);
} }
} }
} }
void COptions::LoadGlobalDefaultOptions(std::map<std::string, unsigned int> cons t& nameOptionMap) void COptions::LoadGlobalDefaultOptions(std::map<std::string, unsigned int> cons t& nameOptionMap)
{ {
CLocalPath const defaultsDir = wxGetApp().GetDefaultsDir(); CLocalPath const defaultsDir = wxGetApp().GetDefaultsDir();
if (defaultsDir.empty()) { if (defaultsDir.empty()) {
return; return;
} }
skipping to change at line 1055 skipping to change at line 1071
if (!dir.empty()) { if (!dir.empty()) {
dir = ExpandPath(dir); dir = ExpandPath(dir);
p.SetPath(wxGetApp().GetDefaultsDir().GetPath()); p.SetPath(wxGetApp().GetDefaultsDir().GetPath());
p.ChangePath(dir); p.ChangePath(dir);
} }
else { else {
p = GetUnadjustedSettingsDir(); p = GetUnadjustedSettingsDir();
} }
if (!p.empty() && !p.Exists()) { if (!p.empty() && !p.Exists()) {
wxFileName::Mkdir(p.GetPath(), 0700, wxPATH_MKDIR_FULL); fz::mkdir(fz::to_native(p.GetPath()), true, true);
} }
SetOption(OPTION_DEFAULT_SETTINGSDIR, p.GetPath()); SetOption(OPTION_DEFAULT_SETTINGSDIR, p.GetPath());
return p; return p;
} }
void COptions::SetDefaultValues() void COptions::SetDefaultValues()
{ {
fz::scoped_lock l(m_sync_); fz::scoped_lock l(m_sync_);
for (int i = 0; i < OPTIONS_NUM; ++i) { for (int i = 0; i < OPTIONS_NUM; ++i) {
m_optionsCache[i].from_default = false; m_optionsCache[i].from_default = false;
if (options[i].type == xml) { if (options[i].type == xml) {
m_optionsCache[i].xmlValue = std::make_unique<pugi::xml_d m_optionsCache[i].xmlValue.reset();
ocument>(); m_optionsCache[i].xmlValue.load_string(fz::to_string(opti
m_optionsCache[i].xmlValue->load_string(fz::to_string(opt ons[i].defaultValue).c_str());
ions[i].defaultValue).c_str());
} }
else { else {
m_optionsCache[i] = options[i].defaultValue; m_optionsCache[i] = options[i].defaultValue;
} }
} }
} }
void COptions::RequireCleanup() void COptions::RequireCleanup()
{ {
needsCleanup_ = true; needsCleanup_ = true;
 End of changes. 53 change blocks. 
139 lines changed or deleted 150 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)