RetroFe now accepts Themes from <Home directory>/layouts. For the FunKey S, this means any new themes can be added to /mnt/FunKey/.retrofe/layouts. Can't wait to see what's going to come out of this

This commit is contained in:
Vincent-FK 2021-06-16 23:48:23 +02:00
parent f162704430
commit 6e920b7c3b
12 changed files with 156 additions and 78 deletions

View File

@ -20,6 +20,7 @@
#include <locale>
#include <fstream>
#include <sstream>
#include <sys/stat.h>
#ifdef WIN32
#include <windows.h>
@ -33,6 +34,8 @@
#endif
std::string Configuration::absolutePath;
std::string Configuration::userPath;
bool Configuration::isUserLayout_ = false;
Configuration::Configuration()
{
@ -54,13 +57,13 @@ void Configuration::initialize()
{
absolutePath = environment;
}
#if defined(__linux) || defined(__APPLE__)
/*#if defined(__linux) || defined(__APPLE__)
// Or check for home based flat file works on linux/mac
else if (retrofe_path && std::getline( retrofe_path, absolutePath ))
{
retrofe_path.close();
retrofe_path.close();
}
#endif
#endif*/
// Or check executable for path
else
{
@ -94,6 +97,13 @@ void Configuration::initialize()
absolutePath = sPath;
}
/** Get user path */
struct stat info;
if(stat(home_load.c_str(), &info) == 0){
//if(IsPathExist(home_load)){
userPath = home_load;
}
}
bool Configuration::import(std::string keyPrefix, std::string file)
@ -136,7 +146,7 @@ bool Configuration::import(std::string collection, std::string keyPrefix, std::s
return retVal;
}
bool Configuration::importLayouts(std::string folder, std::string file, bool mustExist)
bool Configuration::importLayouts(std::string folder, std::string file, bool userLayout, bool mustExist)
{
bool retVal = true;
int lineCount = 0;
@ -169,21 +179,32 @@ bool Configuration::importLayouts(std::string folder, std::string file, bool mus
if(line.empty() || (line.find_first_not_of(" \t\r") == std::string::npos))
{
retVal = true;
retVal = true;
}
else
{
std::string layoutName = trimEnds(line);
std::string layoutPath = Utils::combinePath(folder, layoutName);
/* Set new layoutPath */
layouts_.push_back(layoutPath);
std::stringstream ss;
ss << "Dump layouts: " << "\"" << layoutPath << "\"";
std::string layoutName = trimEnds(line);
std::string layoutPath = Utils::combinePath(folder, layoutName);
Logger::write(Logger::ZONE_INFO, "Configuration", ss.str());
retVal = true;
/** Check if dir exists */
struct stat info;
if(stat(layoutPath.c_str(), &info) != 0){
//if(!IsPathExist(layoutPath)){
printf("Layout path: %s does not exist\n", layoutPath.c_str());
Logger::write(Logger::ZONE_ERROR, "Configuration", "Layout path: " + layoutPath + " does not exist");
continue;
}
/* Set new layoutPath */
layouts_.push_back( LayoutPair(layoutPath, userLayout) );
std::stringstream ss;
ss << "Dump layouts: " << "\"" << layoutPath << "\"";
Logger::write(Logger::ZONE_INFO, "Configuration", ss.str());
retVal = true;
}
}
@ -230,46 +251,74 @@ bool Configuration::importCurrentLayout(std::string folder, std::string file, bo
{
retVal = false;
}
// finding layout in existing list
// Check if layout is in existing list
else
{
std::string seekedLayoutName = trimEnds(line);
std::string layoutPathFound;
bool layoutFoundInList = false;
bool userLayout = false;
std::string layoutPath;
// check existing layout list */
for(std::vector<std::string>::iterator it = layouts_.begin(); it != layouts_.end(); ++it){
std::string curLayoutName = Utils::getFileName(*it);
/** Check existing layout list */
for(std::vector<LayoutPair>::iterator it = layouts_.begin(); it != layouts_.end(); ++it){
std::string curLayoutName = Utils::getFileName((*it).first);
userLayout = (*it).second;
if(!curLayoutName.compare(seekedLayoutName)){
layoutPathFound = *it;
layoutFoundInList = true;
break;
}
index++;
}
if (layoutFoundInList){
/** Reset default theme if not found in list */
if(!layoutFoundInList){
Logger::write(Logger::ZONE_ERROR, "Configuration", "Layout \"" + seekedLayoutName + "\" not found in list! Resetting \"Classic\" theme by default");
printf("Layout \"%s\" not found in list!\n", seekedLayoutName.c_str());
/* remove layout properties if they already exist */
if(properties_.find("layout") != properties_.end())
{
properties_.erase("layout");
}
seekedLayoutName = std::string("Classic");
userLayout = false;
layoutPath = Utils::combinePath(Configuration::absolutePath, "layouts", seekedLayoutName);
index = 0;
}
/* Set new pair <key, value> for key = layout */
properties_.insert(PropertiesPair("layout", seekedLayoutName));
/** Check if Layout path exists */
if(userLayout){
layoutPath = Utils::combinePath(Configuration::userPath, "layouts", seekedLayoutName);
}
else{
layoutPath = Utils::combinePath(Configuration::absolutePath, "layouts", seekedLayoutName);
}
printf("Layout directory is \"%s\" \n", layoutPath.c_str());
struct stat info;
if(stat(layoutPath.c_str(), &info) != 0){
//if(IsPathExist(layoutPath)){
Logger::write(Logger::ZONE_ERROR, "Configuration", "Layout directory\"" + layoutPath + "\" was not found! Resetting \"Classic\" theme by default");
printf("Layout directory \"%s\" was not found!\n", layoutPath.c_str());
}
std::stringstream ss;
//printf("Found layout: %s at idx %d\n", layoutPathFound.c_str(), index);
ss << "Found layout: " << "\"" << layoutPathFound << "\" in layouts list at idx " << index;
Logger::write(Logger::ZONE_INFO, "Configuration", ss.str());
retVal = true;
/* Remove layout properties if they already exist */
if(properties_.find("layout") != properties_.end())
{
properties_.erase("layout");
}
if(properties_.find("userTheme") != properties_.end())
{
properties_.erase("userTheme");
}
break;
}
else{
index = 0;
}
/* Set new pair <key, value> for key = layout */
properties_.insert(PropertiesPair("layout", seekedLayoutName));
properties_.insert(PropertiesPair("userTheme", userLayout?"yes":"no"));
Configuration::isUserLayout_ = userLayout;
std::stringstream ss;
//printf("Found layout: %s at idx %d\n", layoutPathFound.c_str(), index);
ss << "Found layout: " << "\"" << layoutPath << "\" in layouts list at idx " << index;
Logger::write(Logger::ZONE_INFO, "Configuration", ss.str());
retVal = true;
break;
}
}

View File

@ -30,7 +30,7 @@ public:
// gets the global configuration
bool import(std::string keyPrefix, std::string file);
bool import(std::string collection, std::string keyPrefix, std::string file, bool mustExist = true);
bool importLayouts(std::string folder, std::string file, bool mustExist = true);
bool importLayouts(std::string folder, std::string file, bool userLayout=false, bool mustExist = true);
bool importCurrentLayout(std::string folder, std::string file, bool mustExist = true);
bool exportCurrentLayout(std::string layoutFilePath, std::string layoutName);
bool getProperty(std::string key, std::string &value);
@ -45,7 +45,10 @@ public:
void getMediaPropertyAbsolutePath(std::string collectionName, std::string mediaType, bool system, std::string &value);
void getCollectionAbsolutePath(std::string collectionName, std::string &value);
static std::string absolutePath;
std::vector<std::string> layouts_;
static std::string userPath;
static bool isUserLayout_;
typedef std::pair<std::string, bool> LayoutPair;
std::vector<LayoutPair> layouts_;
int currentLayoutIdx_;
private:

View File

@ -419,7 +419,7 @@ void ReloadableMedia::reloadTexture( bool previousItem )
{
std::string imagePath;
ImageBuilder imageBuild;
imagePath = Utils::combinePath(Configuration::absolutePath, "collections", collectionName );
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "collections", collectionName );
imagePath = Utils::combinePath( imagePath, "system_artwork" );
loadedComponent_ = imageBuild.CreateImage( imagePath, page, std::string("fallback"), scaleX_, scaleY_, ditheringAuthorized_ );
}
@ -448,11 +448,11 @@ Component *ReloadableMedia::findComponent(std::string collection, std::string ty
config_.getProperty("layout", layoutName);
if (commonMode_)
{
imagePath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
}
else
{
imagePath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", collection);
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", collection);
}
if (systemMode)
imagePath = Utils::combinePath(imagePath, "system_artwork");
@ -463,7 +463,7 @@ Component *ReloadableMedia::findComponent(std::string collection, std::string ty
{
if (commonMode_)
{
imagePath = Utils::combinePath(Configuration::absolutePath, "collections", "_common" );
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "collections", "_common" );
if (systemMode)
imagePath = Utils::combinePath(imagePath, "system_artwork");
else

View File

@ -483,7 +483,7 @@ void ReloadableScrollingText::loadText( std::string collection, std::string type
{
std::string layoutName;
config_.getProperty("layout", layoutName);
textPath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", collection);
textPath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", collection);
if (systemMode)
textPath = Utils::combinePath(textPath, "system_artwork");
else

View File

@ -658,9 +658,9 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
if ( layoutMode_ )
{
if ( commonMode_ )
imagePath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
else
imagePath = Utils::combinePath( Configuration::absolutePath, "layouts", layoutName, "collections", collectionName );
imagePath = Utils::combinePath( Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", collectionName );
imagePath = Utils::combinePath( imagePath, "medium_artwork", imageType_ );
}
@ -668,7 +668,7 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
{
if ( commonMode_ )
{
imagePath = Utils::combinePath(Configuration::absolutePath, "collections", "_common" );
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "collections", "_common" );
imagePath = Utils::combinePath( imagePath, "medium_artwork", imageType_ );
}
else
@ -681,7 +681,7 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
{
if ( layoutMode_ )
{
imagePath = Utils::combinePath( Configuration::absolutePath, "layouts", layoutName, "collections", item->collectionInfo->name );
imagePath = Utils::combinePath( Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", item->collectionInfo->name );
imagePath = Utils::combinePath( imagePath, "medium_artwork", imageType_ );
}
else
@ -698,10 +698,10 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
if ( layoutMode_ )
{
if ( commonMode_ ){
imagePath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", "_common");
}
else{
imagePath = Utils::combinePath( Configuration::absolutePath, "layouts", layoutName, "collections", item->name );
imagePath = Utils::combinePath( Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutName, "collections", item->name );
}
imagePath = Utils::combinePath( imagePath, "system_artwork" );
}
@ -709,7 +709,7 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
{
if ( commonMode_ )
{
imagePath = Utils::combinePath(Configuration::absolutePath, "collections", "_common" );
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "collections", "_common" );
imagePath = Utils::combinePath( imagePath, "system_artwork" );
}
else{
@ -726,7 +726,7 @@ bool ScrollingList::allocateTexture( unsigned int index, Item *item )
// Image fallback
if ( !t && imageType_.compare(std::string("null"))){
imagePath = Utils::combinePath(Configuration::absolutePath, "collections", collectionName );
imagePath = Utils::combinePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, "collections", collectionName );
imagePath = Utils::combinePath( imagePath, "system_artwork" );
t = imageBuild.CreateImage( imagePath, page, std::string("fallback"), scaleX_, scaleY_, ditheringAuthorized_ );
}

View File

@ -50,7 +50,7 @@ static const int MENU_END = -2; // last item transitions here after it scroll
static const int MENU_CENTER = -4;
//todo: this file is starting to become a god class of building. Consider splitting into sub-builders
PageBuilder::PageBuilder(std::string layoutKey, std::string layoutPage, Configuration &c, FontCache *fc, bool isMenu)
PageBuilder::PageBuilder(std::string layoutKey, std::string layoutPage, Configuration &c, FontCache *fc, bool isMenu, bool userLayout)
: layoutKey(layoutKey)
, layoutPage(layoutPage)
, config_(c)
@ -61,6 +61,7 @@ PageBuilder::PageBuilder(std::string layoutKey, std::string layoutPage, Configur
, fontSize_(24)
, fontCache_(fc)
, isMenu_(isMenu)
, userLayout_(userLayout)
{
screenWidth_ = SDL::getWindowWidth();
screenHeight_ = SDL::getWindowHeight();
@ -82,20 +83,25 @@ Page *PageBuilder::buildPage( std::string collectionName )
std::string layoutFileAspect;
std::string layoutName = layoutKey;
std::string originPath = userLayout_?Configuration::userPath:Configuration::absolutePath;
if ( isMenu_ )
{
layoutPath = Utils::combinePath(Configuration::absolutePath, "menu");
layoutPath = Utils::combinePath(originPath, "menu");
}
else if ( collectionName == "" )
{
layoutPath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName);
layoutPath = Utils::combinePath(originPath, "layouts", layoutName);
}
else
{
layoutPath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, "collections", collectionName);
layoutPath = Utils::combinePath(originPath, "layouts", layoutName, "collections", collectionName);
layoutPath = Utils::combinePath(layoutPath, "layout");
}
layoutFile = Utils::combinePath(layoutPath, layoutPage + ".xml");
printf("In %s, layoutFile is %s \n", __func__, layoutFile.c_str());
if ( screenWidth_*3/4 == screenHeight_ )
layoutFileAspect = Utils::combinePath(layoutPath, layoutPage + " 4x3.xml");
else if ( screenWidth_*4/3 == screenHeight_ )
@ -167,7 +173,7 @@ Page *PageBuilder::buildPage( std::string collectionName )
if(fontXml)
{
fontName_ = config_.convertToAbsolutePath(
Utils::combinePath(config_.absolutePath, "layouts", layoutKey, ""),
Utils::combinePath(originPath, "layouts", layoutKey, ""),
fontXml->value());
}
@ -221,7 +227,7 @@ Page *PageBuilder::buildPage( std::string collectionName )
std::string file = Configuration::convertToAbsolutePath(layoutPath, src->value());
std::string layoutName;
config_.getProperty("layout", layoutName);
std::string altfile = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, std::string(src->value()));
std::string altfile = Utils::combinePath(originPath, "layouts", layoutName, std::string(src->value()));
if(!type)
{
Logger::write(Logger::ZONE_ERROR, "Layout", "Sound tag missing type attribute");
@ -456,7 +462,9 @@ bool PageBuilder::buildComponents(xml_node<> *layout, Page *page)
std::string layoutName;
config_.getProperty("layout", layoutName);
std::string altImagePath;
altImagePath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, std::string(src->value()));
altImagePath = Utils::combinePath(userLayout_?Configuration::userPath:Configuration::absolutePath,
"layouts", layoutName, std::string(src->value()));
Image *c = new Image(imagePath, altImagePath, *page, scaleX_, scaleY_, dithering);
c->setId( id );
@ -497,7 +505,8 @@ bool PageBuilder::buildComponents(xml_node<> *layout, Page *page)
std::string layoutName;
config_.getProperty("layout", layoutName);
std::string altVideoPath;
altVideoPath = Utils::combinePath(Configuration::absolutePath, "layouts", layoutName, std::string(srcXml->value()));
altVideoPath = Utils::combinePath(userLayout_?Configuration::userPath:Configuration::absolutePath,
"layouts", layoutName, std::string(srcXml->value()));
int numLoops = numLoopsXml ? Utils::convertInt(numLoopsXml->value()) : 1;
Video *c = new Video(videoPath, altVideoPath, numLoops, *page, scaleX_, scaleY_);
@ -891,7 +900,7 @@ Font *PageBuilder::addFont(xml_node<> *component, xml_node<> *defaults)
if(fontXml)
{
fontName = config_.convertToAbsolutePath(
Utils::combinePath(config_.absolutePath, "layouts", layoutKey,""),
Utils::combinePath(userLayout_?Configuration::userPath:Configuration::absolutePath, "layouts", layoutKey,""),
fontXml->value());
Logger::write(Logger::ZONE_DEBUG, "Layout", "loading font " + fontName );

View File

@ -33,7 +33,7 @@ class Font;
class PageBuilder
{
public:
PageBuilder(std::string layoutKey, std::string layoutPage, Configuration &c, FontCache *fc, bool isMenu = false);
PageBuilder(std::string layoutKey, std::string layoutPage, Configuration &c, FontCache *fc, bool isMenu = false, bool userLayout = false);
virtual ~PageBuilder();
Page *buildPage( std::string collectionName = "" );
@ -51,6 +51,7 @@ private:
int fontSize_;
FontCache *fontCache_;
bool isMenu_;
bool userLayout_;
Font *addFont(rapidxml::xml_node<> *component, rapidxml::xml_node<> *defaults);
void loadReloadableImages(rapidxml::xml_node<> *layout, std::string tagName, Page *page);

View File

@ -139,11 +139,13 @@ bool ImportConfiguration(Configuration *c)
}
/* Read layouts on user partition */
std::string layoutUserPath = Utils::combinePath(std::string("/mnt"), "themes");
std::string layoutListUserPath = Utils::combinePath(layoutUserPath, "themes.list");
if(!c->importLayouts(layoutUserPath, layoutListUserPath))
{
Logger::write(Logger::ZONE_ERROR, "RetroFE", "Could not import \"" + layoutListUserPath + "\"");
if(!Configuration::userPath.empty()){
std::string layoutUserPath = Utils::combinePath(Configuration::userPath, "layouts");
std::string layoutListUserPath = Utils::combinePath(layoutUserPath, "layouts.list");
if(!c->importLayouts(layoutUserPath, layoutListUserPath, true))
{
Logger::write(Logger::ZONE_ERROR, "RetroFE", "Could not import \"" + layoutListUserPath + "\"");
}
}
/* Read current layout */
@ -260,6 +262,9 @@ bool StartLogging()
#endif
Logger::write(Logger::ZONE_INFO, "RetroFE", "Absolute path: " + Configuration::absolutePath);
if(!Configuration::userPath.empty()){
Logger::write(Logger::ZONE_INFO, "RetroFE", "User path: " + Configuration::userPath);
}
return true;
}

View File

@ -609,7 +609,7 @@ void MenuMode::menu_screen_refresh(int menuItem, int prevItem, int scroll, uint8
case MENU_TYPE_THEME:
/// ---- Write current chosen theme -----
curLayoutName = (char*)Utils::getFileName(config->layouts_.at(indexChooseLayout)).c_str();
curLayoutName = (char*)Utils::getFileName( (config->layouts_.at(indexChooseLayout)).first ).c_str();
// no more than max_chars chars in name to fit screen
if(strlen(curLayoutName) > max_chars){
@ -1016,7 +1016,7 @@ int MenuMode::launch( )
/// ----- Write new theme and restart RetroFe ----
config->exportCurrentLayout(Utils::combinePath(Configuration::absolutePath, "layout.conf"),
Utils::getFileName(config->layouts_.at(indexChooseLayout)));
Utils::getFileName( (config->layouts_.at(indexChooseLayout)).first ));
stop_menu_loop = 1;
returnCode = MENU_RETURN_EXIT;
}

View File

@ -677,8 +677,10 @@ void RetroFE::run( )
{
// Load new layout if available
std::string layoutName;
bool userLayout;
config_.getProperty( "layout", layoutName );
PageBuilder pb( layoutName, "layout", config_, &fontcache_ );
config_.getProperty( "userTheme", userLayout );
PageBuilder pb( layoutName, "layout", config_, &fontcache_, false, userLayout);
Page *page = pb.buildPage( nextPageItem_->name );
if ( page )
{
@ -1360,10 +1362,10 @@ RetroFE::RETROFE_STATE RetroFE::processUserInput( Page *page )
Page *RetroFE::loadPage( )
{
std::string layoutName;
bool userLayout;
config_.getProperty( "layout", layoutName );
PageBuilder pb( layoutName, "layout", config_, &fontcache_ );
config_.getProperty( "userTheme", userLayout );
PageBuilder pb( layoutName, "layout", config_, &fontcache_, false, userLayout);
Page *page = pb.buildPage( );
if ( !page )
@ -1379,9 +1381,10 @@ Page *RetroFE::loadPage( )
Page *RetroFE::loadSplashPage( )
{
std::string layoutName;
bool userLayout;
config_.getProperty( "layout", layoutName );
PageBuilder pb( layoutName, "splash", config_, &fontcache_ );
config_.getProperty( "userTheme", userLayout );
PageBuilder pb( layoutName, "splash", config_, &fontcache_, false, userLayout);
Page * page = pb.buildPage( );
page->start( );

View File

@ -26,6 +26,7 @@
#include <list>
#include <linux/vt.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
@ -95,7 +96,6 @@ std::string Utils::combinePath(std::list<std::string> &paths)
it++;
}
while(it != paths.end())
{
path += Utils::pathSeparator;
@ -149,7 +149,7 @@ bool Utils::findMatchingFile(std::string prefix, std::vector<std::string> &exten
for(unsigned int i = 0; i < extensions.size(); ++i)
{
std::string temp = prefix + "." + extensions[i];
temp = Configuration::convertToAbsolutePath(Configuration::absolutePath, temp);
temp = Configuration::convertToAbsolutePath(Configuration::isUserLayout_?Configuration::userPath:Configuration::absolutePath, temp);
std::ifstream f(temp.c_str());
@ -158,6 +158,7 @@ bool Utils::findMatchingFile(std::string prefix, std::vector<std::string> &exten
file = temp;
return true;
}
//printf("In %s, file %s not found\n", __func__, temp.c_str());
}
return false;
@ -286,6 +287,12 @@ std::string Utils::trimEnds(std::string str)
return str;
}
bool Utils::IsPathExist(const std::string &s)
{
struct stat buffer;
return (stat (s.c_str(), &buffer) == 0);
}
bool Utils::executeRawPath(const char *shellCmd)
{

View File

@ -53,7 +53,8 @@ public:
static std::string combinePath(std::string path1, std::string path2, std::string path3);
static std::string combinePath(std::string path1, std::string path2, std::string path3, std::string path4);
static std::string combinePath(std::string path1, std::string path2, std::string path3, std::string path4, std::string path5);
static bool IsPathExist(const std::string &s);
static bool executeRawPath(const char *shellCmd);
static bool rootfsWritable();
static bool rootfsReadOnly();