/* * Copyright (c) 2006 Sean C. Rhea (srhea@srhea.net) * Copyright (c) 2015 Joern Rischmueller (joern.rm@gmail.com) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., 51 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "Settings.h" #include "MainWindow.h" #include "Colors.h" #include #include #ifdef Q_OS_MAC int OperatingSystem = OSX; #elif defined Q_OS_WIN32 int OperatingSystem = WINDOWS; #elif defined Q_OS_LINUX int OperatingSystem = LINUX; #elif defined Q_OS_OPENBSD int OperatingSystem = OPENBSD; #endif // -------------- Initializer for the "extern" variable "appsettings" ----------------// static GSettings *GetApplicationSettings() { GSettings *settings; QDir home = QDir(); //First check to see if the Library folder exists where the executable is (for USB sticks) if(!home.exists("Library/GoldenCheetah")) settings = new GSettings(GC_SETTINGS_CO, GC_SETTINGS_APP); else settings = new GSettings(home.absolutePath()+"/gc", QSettings::IniFormat); return settings; } // local static helper routines // define the sections and the filenames enum SettingsType {SETTINGS_SYSTEM = 0, SETTINGS_GLOBAL = 1, SETTINGS_ATHLETE = 2 }; enum SettingsFilesIndexGlobal {GLOBAL_GENERAL = 0, GLOBAL_TRAINMODE = 1}; enum SettingsFilesIndexAthlete { ATHLETE_GENERAL = 0, ATHLETE_LAYOUT = 1, ATHLETE_PREFERENCES = 2, ATHLETE_PRIVATE = 3}; static const QString settingFileNamesGlobal[2] = {"configglobal-general.ini","configglobal-trainmode.ini"}; static const QString settingFileNamesAthlete[4] = {"athlete-general.ini","athlete-layout.ini","athlete-preferences.ini","athlete-private.ini"}; static QString DetermineKey(QString & key, int& store, int& fileIndex) { store = SETTINGS_SYSTEM; // default to systemsettings fileIndex = 0; if (key.startsWith(GC_QSETTINGS_GLOBAL_GENERAL)) { store = SETTINGS_GLOBAL; fileIndex = GLOBAL_GENERAL; } else if (key.startsWith(GC_QSETTINGS_GLOBAL_TRAIN)) { store = SETTINGS_GLOBAL; fileIndex = GLOBAL_TRAINMODE; } else if (key.startsWith(GC_QSETTINGS_ATHLETE_GENERAL)) { store = SETTINGS_ATHLETE; fileIndex = ATHLETE_GENERAL; } else if (key.startsWith(GC_QSETTINGS_ATHLETE_LAYOUT)) { store = SETTINGS_ATHLETE; fileIndex = ATHLETE_LAYOUT; } else if (key.startsWith(GC_QSETTINGS_ATHLETE_PREFERENCES)) { store = SETTINGS_ATHLETE; fileIndex = ATHLETE_PREFERENCES; } else if (key.startsWith(GC_QSETTINGS_ATHLETE_PRIVATE)) { store = SETTINGS_ATHLETE; fileIndex = ATHLETE_PRIVATE; } // and make sure <> text is removed return key.remove(QRegExp("^<.*>")); } // -----------------------------constructor and public instance methods ------------------------// GSettings::GSettings(QString org, QString app) : newFormat(true){ oldsystemsettings = new QSettings(org,app); systemsettings = new QSettings(QSettings::IniFormat, QSettings::UserScope, org, app); global = new QVector(); } GSettings::GSettings(QString file, QSettings::Format format) : newFormat(false){ systemsettings = new QSettings(file,format); } GSettings::~GSettings() { syncQSettings(); } QVariant GSettings::value(const QObject * /*me*/, const QString key, const QVariant def) { QString keyVar = QString(key); if (newFormat) { int store; int file; keyVar = DetermineKey(keyVar, store, file); switch (store) { case SETTINGS_SYSTEM: return systemsettings->value(keyVar, def); break; case SETTINGS_GLOBAL: return global->at(file)->value(keyVar, def); break; case SETTINGS_ATHLETE: qDebug() << "GetValue key, keyVar, store:" << key << ":" << keyVar << ": " << store; // error cases on code configuration break; } } else { keyVar.remove(QRegExp("^<.*>")); return systemsettings->value(keyVar, def); } return QVariant(); } void GSettings::setValue(QString key, QVariant value) { QString keyVar = QString(key); if (newFormat) { int store; int file; keyVar = DetermineKey(keyVar, store, file); switch (store) { case SETTINGS_SYSTEM: systemsettings->setValue(keyVar,value); break; case SETTINGS_GLOBAL: global->at(file)->setValue(keyVar,value); break; case SETTINGS_ATHLETE: qDebug() << "SetValue key, keyVar, store:" << key << ":" << keyVar << ": " << store; // error cases on code configuration break; } } else { keyVar.remove(QRegExp("^<.*>")); systemsettings->setValue(keyVar, value); } } // access to athlete specific config QVariant GSettings::cvalue(QString athleteName, QString key, QVariant def) { if (athleteName.isNull() || athleteName.isEmpty()) return def; QString keyVar = QString(key); if (newFormat) { int store; int file; keyVar = DetermineKey(keyVar, store, file); QHash::const_iterator i = athlete.find(athleteName); if (i != athlete.end()) { switch (store) { case SETTINGS_SYSTEM: case SETTINGS_GLOBAL: qDebug() << "GetCValue key, keyVar, store:" << key << ":" << keyVar << ": " << store; // error cases on code configuration break; case SETTINGS_ATHLETE: return i.value()->getQSettings(file)->value(keyVar, def); break; } } else { // fall back to old settings - assuming that this can only happen during the upgrade of an athlete // and before the new /config folder exists return oldsystemsettings->value(athleteName+"/"+keyVar, def); } } else { keyVar.remove(QRegExp("^<.*>")); return systemsettings->value(athleteName+"/"+keyVar, def); } return QVariant(); } void GSettings::setCValue(QString athleteName, QString key, QVariant value) { QString keyVar = QString(key); if (newFormat) { int store; int file; keyVar = DetermineKey(keyVar, store, file); QHash::const_iterator i = athlete.find(athleteName); if (i != athlete.end()) { switch (store) { case SETTINGS_SYSTEM: case SETTINGS_GLOBAL: qDebug() << "SetCValue keyVar, store:" << key << ":" << keyVar << ": " << store; // error cases on code configuration break; case SETTINGS_ATHLETE: i.value()->getQSettings(file)->setValue(keyVar, value); break; } } // if we do have have the athlete - then we do not store anything } else { keyVar.remove(QRegExp("^<.*>")); systemsettings->setValue(athleteName + "/" + keyVar,value); } } // other functions unsed from QSettings which GSettings needs to implement QStringList GSettings::allKeys() const { if (newFormat) { QStringList allKeys, tempKeys; tempKeys = systemsettings->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_SYSTEM+key); } tempKeys = global->at(GLOBAL_GENERAL)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_GLOBAL_GENERAL+key); } tempKeys = global->at(GLOBAL_TRAINMODE)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_GLOBAL_TRAIN+key); } QHashIterator i(athlete); i.toFront(); while (i.hasNext()) { i.next(); tempKeys = i.value()->getQSettings(ATHLETE_GENERAL)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_ATHLETE_GENERAL+key); } tempKeys = i.value()->getQSettings(ATHLETE_LAYOUT)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_ATHLETE_LAYOUT+key); } tempKeys = i.value()->getQSettings(ATHLETE_PREFERENCES)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_ATHLETE_PREFERENCES+key); } tempKeys = i.value()->getQSettings(ATHLETE_PRIVATE)->allKeys(); foreach (QString key, tempKeys) { allKeys.append(GC_QSETTINGS_ATHLETE_PRIVATE+key); } } allKeys.removeDuplicates(); // remove duplicate keys from the Athlete Settings return allKeys; } else { return systemsettings->allKeys(); } return QStringList(); } bool GSettings::contains(const QString & key) const { QString keyVar = QString(key); if (newFormat) { int store; int file; keyVar = DetermineKey(keyVar, store, file); switch (store) { case SETTINGS_SYSTEM: return systemsettings->contains(keyVar); break; case SETTINGS_GLOBAL: return global->at(file)->contains(keyVar); break; case SETTINGS_ATHLETE: qDebug() << "Contains Value key:" << key << "keyVar:" << keyVar; // error cases on code configuration return false; break; } } else { keyVar.remove(QRegExp("^<.*>")); return systemsettings->contains(keyVar); } return false; } void GSettings::migrateQSettingsSystem() { if (!newFormat) return; // do the migration for the System Settings - if not yet done // - System is only migrated once per PC (since it only exists once // on MAC GC_CHROME is already set previously - so migrate anyway bool migrateMac = false; QStringList currentKeys = systemsettings->allKeys(); #ifdef Q_OS_MAC migrateMac = true; #endif if (currentKeys.size() == 0 || (migrateMac && currentKeys.size() == 1)) { upgradeSystem(); systemsettings->sync(); } } void GSettings::initializeQSettingsGlobal(QString athletesRootDir) { if (!newFormat) return; if (global->isEmpty()) { global->append(new QSettings(athletesRootDir+"/"+settingFileNamesGlobal[GLOBAL_GENERAL],QSettings::IniFormat)); global->append(new QSettings(athletesRootDir+"/"+settingFileNamesGlobal[GLOBAL_TRAINMODE],QSettings::IniFormat)); } // do the migration for the AthleteDir / Global Settings if not yet done // - Global is migrated to the root of ANY AthleteDirectory if it does not yet exist // this is too support migration if a user has multiple AthleteDirectories in place // but also creates a default like his previous old-style directory when new AthleteDirectory is created if (global->at(GLOBAL_GENERAL)->allKeys().isEmpty() && global->at(GLOBAL_TRAINMODE)->allKeys().isEmpty()) { upgradeGlobal(); } syncQSettingsGlobal(); } void GSettings::initializeQSettingsAthlete(QString athletesRootDir, QString athleteName) { // assumption is that the directory "/athleteName" exists // if (!newFormat) return; // handle not yet upgraded athlete folders without causing problems // initializing of the QSettings would work anyway - but upgrade would fail, // since the /config folder does not exist - so leave the upgrade for the next // initialization after successfull upgrade (the case should be rare anyway) QDir configDir(athletesRootDir+"/"+athleteName+"/config"); if (!configDir.exists()) { return; // athlete has not yet been migrated and /config does not exist - so wait until next time } // create the New Athlete QSettings (if they do not yet exists and migrate the old data if required) QHash::const_iterator i = athlete.find(athleteName); if (i == athlete.end()) { initializeQSettingsNewAthlete(athletesRootDir, athleteName); QHash::const_iterator i2 = athlete.find(athleteName); // do the upgrade for the Athlete Properties - but only if the Settings are currently empty - don't overwrite anything if (i2 != athlete.end()) { if (i2.value()->getQSettings(ATHLETE_GENERAL)->allKeys().isEmpty() && i2.value()->getQSettings(ATHLETE_LAYOUT)->allKeys().isEmpty() && i2.value()->getQSettings(ATHLETE_PREFERENCES)->allKeys().isEmpty() && i2.value()->getQSettings(ATHLETE_PRIVATE)->allKeys().isEmpty() ) { upgradeAthlete(athleteName); } } syncQSettingsAllAthletes(); } } void GSettings::initializeQSettingsNewAthlete(QString athletesRootDir, QString athleteName) { if (!newFormat) return; // create the Athlete QSettings - they MUST not exist yet AthleteQSettings* athleteSettings = new AthleteQSettings(); QString baseName = athletesRootDir + "/" + athleteName + "/config/"; athleteSettings->setQSettings(new QSettings(baseName+settingFileNamesAthlete[ATHLETE_GENERAL], QSettings::IniFormat), ATHLETE_GENERAL ); athleteSettings->setQSettings(new QSettings(baseName+settingFileNamesAthlete[ATHLETE_LAYOUT], QSettings::IniFormat), ATHLETE_LAYOUT ); athleteSettings->setQSettings(new QSettings(baseName+settingFileNamesAthlete[ATHLETE_PREFERENCES], QSettings::IniFormat), ATHLETE_PREFERENCES ); athleteSettings->setQSettings(new QSettings(baseName+settingFileNamesAthlete[ATHLETE_PRIVATE], QSettings::IniFormat), ATHLETE_PRIVATE ); athlete.insert(athleteName, athleteSettings); } void GSettings::syncQSettingsAllAthletes() { if (!newFormat) return; QHashIterator i(athlete); i.toFront(); while (i.hasNext()) { i.next(); i.value()->getQSettings(ATHLETE_GENERAL)->sync(); i.value()->getQSettings(ATHLETE_LAYOUT)->sync(); i.value()->getQSettings(ATHLETE_PREFERENCES)->sync(); i.value()->getQSettings(ATHLETE_PRIVATE)->sync(); } } void GSettings::syncQSettingsGlobal() { if (!newFormat) return; if (global->size() == 2) { global->at(GLOBAL_GENERAL)->sync(); global->at(GLOBAL_TRAINMODE)->sync(); }; } void GSettings::syncQSettings() { systemsettings->sync(); syncQSettingsGlobal(); syncQSettingsAllAthletes(); } void GSettings::clearGlobalAndAthletes() { if (!newFormat) return; syncQSettings(); global->clear(); athlete.clear(); } /*-------------------------------- special methods for Upgrade/Migration -------------------------- * * The .INI based storage of Settings has been introduced with GoldenCheetah v3.3.0 * * To transition existing settings (in PLISTs (OSX) and Registry (WINDOWS) from the * propriety storage to the common .INI files an automatic migration of Settings takes * place when no Settings are found. The methods executing the migration are implemented here * * Any development starting starting after v3.3 (so v4.0 and onwards) does not need * to take the migration into account, since any newly defined settings are only stored * using the new .INI based technique. * -----------------------------------------------------------------------------------------------*/ void GSettings::migrateValue(QString key) { QString oldKey = key; oldKey.remove(QRegExp("^<.*>")); if (oldsystemsettings->contains(oldKey)) { setValue(key, oldsystemsettings->value(oldKey)); } } void GSettings::migrateCValue(QString athlete, QString key) { QString oldKey = key; oldKey.remove(QRegExp("^<.*>")); if (oldsystemsettings->contains(athlete+"/"+oldKey)) { setCValue(athlete, key, oldsystemsettings->value(athlete+"/"+oldKey)); } } void GSettings::migrateAndRenameCValue(QString athlete, QString wrongKey, QString key) { wrongKey.remove(QRegExp("^<.*>")); if (oldsystemsettings->contains(athlete+"/"+wrongKey)) { setCValue(athlete, key, oldsystemsettings->value(athlete+"/"+wrongKey)); } } void GSettings::migrateValueToCValue(QString athlete, QString key) { QString oldKey = key; oldKey.remove(QRegExp("^<.*>")); if (oldsystemsettings->contains(oldKey)) { setCValue(athlete, key, oldsystemsettings->value(oldKey)); } } void GSettings::migrateCValueToValue(QString athlete, QString key) { // only migrate if the value does not yet exist on the target INI file if (!contains(key)) { QString oldKey = key; oldKey.remove(QRegExp("^<.*>")); oldKey = athlete+"/"+oldKey; if (oldsystemsettings->contains(oldKey)) { setValue(key, oldsystemsettings->value(oldKey)); } } } void GSettings::upgradeSystem() { // by explicitely naming all the properties, and not choosing the "allKeys()" function, // only the properties still in use are migrated - and not any orphans for previous releases // NOTE: Migrating values is only required for settings introduced in GC version until v3.3 migrateValue(GC_HOMEDIR); migrateValue(GC_SETTINGS_LAST); migrateValue(GC_SETTINGS_MAIN_GEOM); migrateValue(GC_SETTINGS_MAIN_STATE); migrateValue(GC_SETTINGS_LAST_IMPORT_PATH); migrateValue(GC_SETTINGS_LAST_WORKOUT_PATH); migrateValue(GC_LAST_DOWNLOAD_DEVICE); migrateValue(GC_LAST_DOWNLOAD_PORT); migrateValue(GC_BE_LASTDIR); migrateValue(GC_BE_LASTFMT); migrateValue(GC_FONT_DEFAULT); migrateValue(GC_FONT_DEFAULT_SIZE); migrateValue(GC_FONT_CHARTLABELS); migrateValue(GC_FONT_CHARTLABELS_SIZE); //DEPRECATED IN V3.5 migrateValue(GC_FONT_TITLES); //DEPRECATED IN V3.5 migrateValue(GC_FONT_CHARTMARKERS); //DEPRECATED IN V3.5 migrateValue(GC_FONT_CALENDAR); //DEPRECATED IN V3.5 migrateValue(GC_FONT_TITLES_SIZE); //DEPRECATED IN V3.5 migrateValue(GC_FONT_CHARTMARKERS_SIZE); //DEPRECATED IN V3.5 migrateValue(GC_FONT_CALENDAR_SIZE); QStringList colorProperties = GCColor::getConfigKeys(); QStringListIterator colorIterator(colorProperties); while (colorIterator.hasNext()) { QString key = QString(colorIterator.next().data()); migrateValue(key); } migrateValue(GC_CHROME); } void GSettings::upgradeGlobal() { // by explicitely naming all the properties, and not choosing the "allKeys()" function, // only the properties still in use are migrated - and not any orphans for previous releases // NOTE: Migrating values is only required for settings introduced in GC version until v3.3 migrateValue(GC_SETTINGS_SUMMARY_METRICS); migrateValue(GC_SETTINGS_BESTS_METRICS); migrateValue(GC_SETTINGS_INTERVAL_METRICS); migrateValue(GC_TABBAR); migrateValue(GC_WBALFORM); migrateValue(GC_BIKESCOREDAYS); migrateValue(GC_BIKESCOREMODE); migrateValue(GC_WARNCONVERT); migrateValue(GC_WARNEXIT); migrateValue(GC_HIST_BIN_WIDTH); migrateValue(GC_WORKOUTDIR); migrateValue(GC_LINEWIDTH); migrateValue(GC_ANTIALIAS); migrateValue(GC_RIDEBG); migrateValue(GC_RIDESCROLL); migrateValue(GC_RIDEHEAD); migrateValue(GC_SHADEZONES); migrateValue(GC_GARMIN_SMARTRECORD); migrateValue(GC_GARMIN_HWMARK); migrateValue(GC_DPFG_TOLERANCE); migrateValue(GC_DPFG_STOP); migrateValue(GC_DPFS_MAX); migrateValue(GC_DPFS_VARIANCE); migrateValue(GC_DPTA); migrateValue(GC_DPPA); migrateValue(GC_DPFHRS_MAX); migrateValue(GC_DPDP_BIKEWEIGHT); migrateValue(GC_DPDP_CRR); migrateValue(GC_LANG); migrateValue(GC_PACE); migrateValue(GC_SWIMPACE); migrateValue(GC_ELEVATION_HYSTERESIS); migrateValue(GC_START_HTTP); // Handle the Dataprocessor dp/%1/apply keys // Handle the RideEditor colmap/%1 keys QStringList dpKeys = oldsystemsettings->allKeys(); QStringListIterator dpKeysIterator(dpKeys); while (dpKeysIterator.hasNext()) { QString key = QString(dpKeysIterator.next().data()); if (key.startsWith("dp/") || key.startsWith("colmap/")) { migrateValue(GC_QSETTINGS_GLOBAL_GENERAL+key); } } // handle the Device configuration migrateValue(GC_DEV_COUNT); QString devCountKey = GC_DEV_COUNT; devCountKey.remove(QRegExp("^<.*>")); QVariant configVal = oldsystemsettings->value(devCountKey); int devicecount; if (configVal.isNull()) { devicecount=0; } else { devicecount = configVal.toInt(); } for (int i = 0; i < devicecount; i++ ) { QString configStr = QString("%1%2").arg(GC_DEV_NAME).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_TYPE).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_WHEEL).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_SPEC).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_PROF).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_DEF).arg(i+1); migrateValue(configStr); configStr = QString("%1%2").arg(GC_DEV_VIRTUAL).arg(i+1); migrateValue(configStr); } migrateValue(FORTIUS_FIRMWARE); migrateValue(TRAIN_MULTI); } void GSettings::upgradeAthlete(QString athlete) { // by explicitely naming all the properties, and not choosing the "allKeys()" function, // only the properties still in use are migrated - and not any orphans for previous releases // NOTE: Migrating values is only required for settings introduced in GC version until v3.3 migrateCValue(athlete, GC_VERSION_USED); migrateCValue(athlete, GC_SAFEEXIT); migrateCValue(athlete, GC_UPGRADE_FOLDER_SUCCESS); migrateCValue(athlete, GC_LTM_LAST_DATE_RANGE); migrateCValue(athlete, GC_LTM_AUTOFILTERS); migrateCValue(athlete, GC_BLANK_ANALYSIS); migrateCValue(athlete, GC_BLANK_TRAIN); migrateCValue(athlete, GC_BLANK_HOME); migrateCValue(athlete, GC_BLANK_DIARY); migrateCValue(athlete, GC_NICKNAME); migrateCValue(athlete, GC_DOB); migrateCValue(athlete, GC_WEIGHT); migrateCValue(athlete, GC_HEIGHT); migrateCValue(athlete, GC_WBALTAU); migrateCValue(athlete, GC_SEX); migrateCValue(athlete, GC_BIO); migrateCValue(athlete, GC_AVATAR); migrateCValue(athlete, GC_DISCOVERY); migrateCValue(athlete, GC_SB_TODAY); migrateCValue(athlete, GC_LTS_DAYS); migrateCValue(athlete, GC_STS_DAYS); migrateCValue(athlete, GC_NAVHEADINGS); migrateCValue(athlete, GC_NAVGROUPBY); migrateCValue(athlete, GC_SORTBY); migrateCValue(athlete, GC_WEBCAL_URL); migrateCValue(athlete, GC_DIARY_VIEW); migrateCValue(athlete, GC_USE_CP_FOR_FTP); migrateAndRenameCValue(athlete, "bavigator/headingwidths", GC_NAVHEADINGWIDTHS); migrateCValueToValue(athlete, GC_UNIT); // Handle the splittersizes keys QStringList splitterKeys = oldsystemsettings->allKeys(); QStringListIterator splitterKeysIterator(splitterKeys); while (splitterKeysIterator.hasNext()) { QString key = QString(splitterKeysIterator.next().data()); if (key.startsWith(athlete + "/mainwindow/splitterSizes") || key.startsWith(athlete+"/splitter")) { key.remove(0, athlete.size()+1); // remove the Athlete name and / from the old setting ! migrateCValue(athlete, GC_QSETTINGS_ATHLETE_LAYOUT+key); } } // --- private --- // migrateCValue(athlete, GC_RWGPSUSER); migrateCValue(athlete, GC_RWGPSPASS); migrateCValue(athlete, GC_VELOHEROUSER); migrateCValue(athlete, GC_VELOHEROPASS); migrateCValue(athlete, GC_WIURL); migrateCValue(athlete, GC_WIUSER); migrateCValue(athlete, GC_WIKEY); migrateCValue(athlete, GC_DVURL); migrateCValue(athlete, GC_DVUSER); migrateCValue(athlete, GC_DVPASS); migrateCValue(athlete, GC_DVCALDAVTYPE); migrateCValue(athlete, GC_DVGOOGLE_CALID); migrateCValue(athlete, GC_GOOGLE_CALENDAR_REFRESH_TOKEN); migrateCValue(athlete, GC_STRAVA_TOKEN); migrateCValue(athlete, GC_CYCLINGANALYTICS_TOKEN); // migrate from system/global to athlete specific settings migrateValueToCValue(athlete, GC_CRANKLENGTH); migrateValueToCValue(athlete, GC_WHEELSIZE); } //----------------------------------------------------------------------------------------------// // initialise with no athlete GSettings *appsettings = GetApplicationSettings();