diff --git a/src/libcalamares/utils/RAII.h b/src/libcalamares/utils/RAII.h index 1cbead4a2..f3adb90f6 100644 --- a/src/libcalamares/utils/RAII.h +++ b/src/libcalamares/utils/RAII.h @@ -14,6 +14,7 @@ #include #include +#include #include /** @brief Convenience to zero out and deleteLater of any QObject-derived-class @@ -58,4 +59,20 @@ struct cBoolSetter /// @brief Blocks signals on a QObject until destruction using cSignalBlocker = QSignalBlocker; +/** @brief Writes a value on destruction to a pointed-to location. + * + * If the pointer is non-null, write the last-given-value if there + * is one to the pointed-to object. + */ +template < typename T > +struct cPointerSetter +{ + std::optional< T > m_value; + T* m_pointer; + + cPointerSetter( T* p ) : m_pointer(p) {} + ~cPointerSetter() { if ( m_pointer && m_value.has_value() ) { *m_pointer = m_value.value(); } } + + const T& operator=(const T& v) { m_value = v; return v; } +}; #endif diff --git a/src/libcalamares/utils/Tests.cpp b/src/libcalamares/utils/Tests.cpp index cdb37f20d..a689505e9 100644 --- a/src/libcalamares/utils/Tests.cpp +++ b/src/libcalamares/utils/Tests.cpp @@ -46,25 +46,27 @@ private Q_SLOTS: void testCommands(); - /** @brief Test that all the UMask objects work correctly. */ + /** @section Test that all the UMask objects work correctly. */ void testUmask(); - /** @brief Tests the entropy functions. */ + /** @section Tests the entropy functions. */ void testEntropy(); void testPrintableEntropy(); void testOddSizedPrintable(); - /** @brief Tests the RAII bits. */ + /** @section Tests the RAII bits. */ void testBoolSetter(); + void testPointerSetter(); - /** @brief Tests the Traits bits. */ + /** @section Tests the Traits bits. */ void testTraits(); + /** @section Testing the variants-methods */ void testVariantStringListCode(); void testVariantStringListYAMLDashed(); void testVariantStringListYAMLBracketed(); - /** @brief Test smart string truncation. */ + /** @section Test smart string truncation. */ void testStringTruncation(); void testStringTruncationShorter(); void testStringTruncationDegenerate(); @@ -360,6 +362,50 @@ LibCalamaresTests::testBoolSetter() QVERIFY( b ); } +void +LibCalamaresTests::testPointerSetter() +{ + int special = 17; + + QCOMPARE( special, 17 ); + { + cPointerSetter p( &special ); + } + QCOMPARE( special, 17 ); + { + cPointerSetter p( &special ); + p = 18; + } + QCOMPARE( special, 18 ); + { + cPointerSetter p( &special ); + p = 20; + p = 3; + } + QCOMPARE( special, 3 ); + { + cPointerSetter p( nullptr ); + } + QCOMPARE( special, 3 ); + { + // "don't do this" .. order of destructors is important + cPointerSetter p( &special ); + cPointerSetter q( &special ); + p = 17; + } + QCOMPARE( special, 17 ); + { + // "don't do this" .. order of destructors is important + cPointerSetter p( &special ); + cPointerSetter q( &special ); + p = 34; + q = 2; + // q destroyed first, then p + } + QCOMPARE( special, 34 ); +} + + /* Demonstration of Traits support for has-a-method or not. * * We have two classes, c1 and c2; one has a method do_the_thing() and the @@ -431,17 +477,31 @@ LibCalamaresTests::testVariantStringListCode() QCOMPARE( getStringList( m, key ), QStringList {} ); m.insert( key, 17 ); QCOMPARE( getStringList( m, key ), QStringList {} ); - m.insert( key, QString( "more strings" ) ); - QCOMPARE( getStringList( m, key ), - QStringList { "more strings" } ); // A single string **can** be considered a stringlist! m.insert( key, QVariant {} ); QCOMPARE( getStringList( m, key ), QStringList {} ); } { - // Things that are stringlists + // Things that are **like** stringlists + QVariantMap m; + m.insert( key, QString( "astring" ) ); + QCOMPARE( getStringList( m, key ).count(), 1 ); + QCOMPARE( getStringList( m, key ), + QStringList { "astring" } ); // A single string **can** be considered a stringlist! + m.insert( key, QString( "more strings" ) ); + QCOMPARE( getStringList( m, key ).count(), 1 ); + QCOMPARE( getStringList( m, key ), + QStringList { "more strings" } ); + m.insert( key, QString() ); + QCOMPARE( getStringList( m, key ).count(), 1 ); + QCOMPARE( getStringList( m, key ), QStringList { QString() } ); + } + + { + // Things that are definitely stringlists QVariantMap m; m.insert( key, QStringList { "aap", "noot" } ); + QCOMPARE( getStringList( m, key ).count(), 2 ); QVERIFY( getStringList( m, key ).contains( "aap" ) ); QVERIFY( !getStringList( m, key ).contains( "mies" ) ); } diff --git a/src/libcalamares/utils/Variant.h b/src/libcalamares/utils/Variant.h index e1261f877..ab9e73f90 100644 --- a/src/libcalamares/utils/Variant.h +++ b/src/libcalamares/utils/Variant.h @@ -25,13 +25,17 @@ namespace CalamaresUtils */ DLLEXPORT bool getBool( const QVariantMap& map, const QString& key, bool d = false ); -/** - * Get a string value from a mapping with a given key; returns @p d if no value. +/** @brief Get a string value from a mapping with a given key; returns @p d if no value. + * + * The value must be an actual string; numbers are not automatically converted to strings, + * nor are lists flattened or converted. */ DLLEXPORT QString getString( const QVariantMap& map, const QString& key, const QString& d = QString() ); -/** - * Get a string list from a mapping with a given key; returns @p d if no value. +/** @brief Get a string list from a mapping with a given key; returns @p d if no value. + * + * This is slightly more lenient that getString(), and a single-string value will + * be returned as a 1-item list. */ DLLEXPORT QStringList getStringList( const QVariantMap& map, const QString& key, const QStringList& d = QStringList() ); diff --git a/src/modules/packagechooser/Config.h b/src/modules/packagechooser/Config.h index 4cb545cb8..5959e3ea4 100644 --- a/src/modules/packagechooser/Config.h +++ b/src/modules/packagechooser/Config.h @@ -72,7 +72,7 @@ public: */ void updateGlobalStorage( const QStringList& selected ) const; /// As updateGlobalStorage() with an empty selection list - void updateGlobalStorage() const { updateGlobalStorage( QStringList() ); } + void fillGSSecondaryConfiguration() const { updateGlobalStorage( QStringList() ); } private: PackageListModel* m_model = nullptr; diff --git a/src/modules/partition/CMakeLists.txt b/src/modules/partition/CMakeLists.txt index bffb2128c..26d4ffae7 100644 --- a/src/modules/partition/CMakeLists.txt +++ b/src/modules/partition/CMakeLists.txt @@ -49,9 +49,11 @@ if ( KPMcore_FOUND AND Qt5DBus_FOUND AND KF5CoreAddons_FOUND AND KF5Config_FOUND TYPE viewmodule EXPORT_MACRO PLUGINDLLEXPORT_PRO SOURCES + Config.cpp + PartitionViewStep.cpp + core/BootLoaderModel.cpp core/ColorUtils.cpp - core/Config.cpp core/DeviceList.cpp core/DeviceModel.cpp core/KPMHelpers.cpp @@ -75,7 +77,6 @@ if ( KPMcore_FOUND AND Qt5DBus_FOUND AND KF5CoreAddons_FOUND AND KF5Config_FOUND gui/PartitionLabelsView.cpp gui/PartitionSizeController.cpp gui/PartitionSplitterWidget.cpp - gui/PartitionViewStep.cpp gui/ResizeVolumeGroupDialog.cpp gui/ScanningDialog.cpp gui/ReplaceWidget.cpp diff --git a/src/modules/partition/core/Config.cpp b/src/modules/partition/Config.cpp similarity index 67% rename from src/modules/partition/core/Config.cpp rename to src/modules/partition/Config.cpp index 0afcee83d..82c0ad846 100644 --- a/src/modules/partition/core/Config.cpp +++ b/src/modules/partition/Config.cpp @@ -9,6 +9,8 @@ #include "Config.h" +#include "core/PartUtils.h" + #include "GlobalStorage.h" #include "JobQueue.h" #include "utils/Logger.h" @@ -180,7 +182,7 @@ Config::setInstallChoice( InstallChoice c ) if ( c != m_installChoice ) { m_installChoice = c; - emit installChoiceChanged( c ); + Q_EMIT installChoiceChanged( c ); ::updateGlobalStorage( c, m_swapChoice ); } } @@ -202,16 +204,98 @@ Config::setSwapChoice( Config::SwapChoice c ) if ( c != m_swapChoice ) { m_swapChoice = c; - emit swapChoiceChanged( c ); + Q_EMIT swapChoiceChanged( c ); ::updateGlobalStorage( m_installChoice, c ); } } -bool -Config::allowManualPartitioning() const +void +Config::setEraseFsTypeChoice( const QString& choice ) +{ + QString canonicalChoice = PartUtils::canonicalFilesystemName( choice, nullptr ); + if ( canonicalChoice != m_eraseFsTypeChoice ) + { + m_eraseFsTypeChoice = canonicalChoice; + Q_EMIT eraseModeFilesystemChanged( canonicalChoice ); + } +} + +static void +fillGSConfigurationEFI( Calamares::GlobalStorage* gs, const QVariantMap& configurationMap ) +{ + // Set up firmwareType global storage entry. This is used, e.g. by the bootloader module. + QString firmwareType( PartUtils::isEfiSystem() ? QStringLiteral( "efi" ) : QStringLiteral( "bios" ) ); + gs->insert( "firmwareType", firmwareType ); + + gs->insert( "efiSystemPartition", CalamaresUtils::getString( configurationMap, "efiSystemPartition", QStringLiteral( "/boot/efi" ) ) ); + + // Read and parse key efiSystemPartitionSize + if ( configurationMap.contains( "efiSystemPartitionSize" ) ) + { + gs->insert( "efiSystemPartitionSize", CalamaresUtils::getString( configurationMap, "efiSystemPartitionSize" ) ); + } + + // Read and parse key efiSystemPartitionName + if ( configurationMap.contains( "efiSystemPartitionName" ) ) + { + gs->insert( "efiSystemPartitionName", CalamaresUtils::getString( configurationMap, "efiSystemPartitionName" ) ); + } +} + +void +Config::fillConfigurationFSTypes(const QVariantMap& configurationMap) { Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - return gs->value( "allowManualPartitioning" ).toBool(); + + + // The defaultFileSystemType setting needs a bit more processing, + // as we want to cover various cases (such as different cases) + QString fsName = CalamaresUtils::getString( configurationMap, "defaultFileSystemType" ); + QString fsRealName; + FileSystem::Type fsType = FileSystem::Type::Unknown; + if ( fsName.isEmpty() ) + { + cWarning() << "Partition-module setting *defaultFileSystemType* is missing, will use ext4"; + fsRealName = PartUtils::canonicalFilesystemName( QStringLiteral("ext4"), &fsType ); + } + else + { + fsRealName = PartUtils::canonicalFilesystemName( fsName, &fsType ); + if ( fsType == FileSystem::Type::Unknown ) + { + cWarning() << "Partition-module setting *defaultFileSystemType* is bad (" << fsName << ") using ext4 instead"; + fsRealName = PartUtils::canonicalFilesystemName( QStringLiteral("ext4"), &fsType ); + } + else if ( fsRealName != fsName ) + { + cWarning() << "Partition-module setting *defaultFileSystemType* changed to" << fsRealName; + } + } + Q_ASSERT( fsType != FileSystem::Type::Unknown ); + m_defaultFsType = fsType; + gs->insert( "defaultFileSystemType", fsRealName ); + + // TODO: canonicalize the names? How is translation supposed to work? + m_eraseFsTypes = CalamaresUtils::getStringList( configurationMap, "availableFileSystemTypes" ); + if ( !m_eraseFsTypes.contains( fsRealName ) ) + { + if ( !m_eraseFsTypes.isEmpty() ) + { + // Explicitly set, and doesn't include the default + cWarning() << "Partition-module *availableFileSystemTypes* does not contain the default" << fsRealName; + m_eraseFsTypes.prepend( fsRealName ); + } + else + { + // Not explicitly set, so it's empty; don't complain + m_eraseFsTypes = QStringList { fsRealName }; + } + } + + Q_ASSERT( !m_eraseFsTypes.isEmpty() ); + Q_ASSERT( m_eraseFsTypes.contains( fsRealName ) ); + m_eraseFsTypeChoice = fsRealName; + Q_EMIT eraseModeFilesystemChanged( m_eraseFsTypeChoice ); } @@ -236,27 +320,18 @@ Config::setConfigurationMap( const QVariantMap& configurationMap ) } setSwapChoice( m_initialSwapChoice ); - Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - gs->insert( "allowManualPartitioning", - CalamaresUtils::getBool( configurationMap, "allowManualPartitioning", true ) ); + m_allowManualPartitioning = CalamaresUtils::getBool( configurationMap, "allowManualPartitioning", true ); - if ( configurationMap.contains( "requiredPartitionTableType" ) - && configurationMap.value( "requiredPartitionTableType" ).type() == QVariant::List ) - { - m_requiredPartitionTableType.clear(); - m_requiredPartitionTableType.append( configurationMap.value( "requiredPartitionTableType" ).toStringList() ); - } - else if ( configurationMap.contains( "requiredPartitionTableType" ) - && configurationMap.value( "requiredPartitionTableType" ).type() == QVariant::String ) - { - m_requiredPartitionTableType.clear(); - m_requiredPartitionTableType.append( configurationMap.value( "requiredPartitionTableType" ).toString() ); - } + Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); + m_requiredPartitionTableType = CalamaresUtils::getStringList( configurationMap, "requiredPartitionTableType" ); gs->insert( "requiredPartitionTableType", m_requiredPartitionTableType ); + + fillGSConfigurationEFI(gs, configurationMap); + fillConfigurationFSTypes( configurationMap ); } void -Config::updateGlobalStorage() const +Config::fillGSSecondaryConfiguration() const { // If there's no setting (e.g. from the welcome page) for required storage // then use ours, if it was set. diff --git a/src/modules/partition/core/Config.h b/src/modules/partition/Config.h similarity index 70% rename from src/modules/partition/core/Config.h rename to src/modules/partition/Config.h index 57230b6e8..3365403fa 100644 --- a/src/modules/partition/core/Config.h +++ b/src/modules/partition/Config.h @@ -12,6 +12,8 @@ #include "utils/NamedEnum.h" +#include + #include #include @@ -24,6 +26,9 @@ class Config : public QObject ///@brief The swap choice (None, Small, Hibernate, ...) which only makes sense when Erase is chosen Q_PROPERTY( SwapChoice swapChoice READ swapChoice WRITE setSwapChoice NOTIFY swapChoiceChanged ) + ///@brief Name of the FS that will be used when erasing type disk (e.g. "default filesystem") + Q_PROPERTY( QString eraseModeFilesystem READ eraseFsType WRITE setEraseFsTypeChoice NOTIFY eraseModeFilesystemChanged ) + Q_PROPERTY( bool allowManualPartitioning READ allowManualPartitioning CONSTANT FINAL ) public: @@ -54,8 +59,19 @@ public: static const NamedEnumTable< SwapChoice >& swapChoiceNames(); using SwapChoiceSet = QSet< SwapChoice >; + using EraseFsTypesSet = QStringList; + void setConfigurationMap( const QVariantMap& ); - void updateGlobalStorage() const; + /** @brief Set GS values where other modules configuration has priority + * + * Some "required" values are duplicated between modules; if some + * othe module hasn't already set the GS value, take a value from + * the partitioning configuration. + * + * Applicable GS keys: + * - requiredStorageGiB + */ + void fillGSSecondaryConfiguration() const; /** @brief What kind of installation (partitioning) is requested **initially**? * @@ -94,20 +110,44 @@ public: */ SwapChoice swapChoice() const { return m_swapChoice; } - ///@brief Is manual partitioning allowed (not explicitly disnabled in the config file)? - bool allowManualPartitioning() const; + /** @brief Get the list of configured FS types to use with *erase* mode + * + * This list is not empty. + */ + EraseFsTypesSet eraseFsTypes() const { return m_eraseFsTypes; } + + /** @brief Currently-selected FS type for *erase* mode + */ + QString eraseFsType() const { return m_eraseFsTypeChoice; } + + /** @brief Configured default FS type (for other modes than erase) + * + * This is not "Unknown" or "Unformatted" + */ + FileSystem::Type defaultFsType() const { return m_defaultFsType; } + + ///@brief Is manual partitioning allowed (not explicitly disabled in the config file)? + bool allowManualPartitioning() const { return m_allowManualPartitioning; } public Q_SLOTS: void setInstallChoice( int ); ///< Translates a button ID or so to InstallChoice void setInstallChoice( InstallChoice ); void setSwapChoice( int ); ///< Translates a button ID or so to SwapChoice void setSwapChoice( SwapChoice ); + void setEraseFsTypeChoice( const QString& filesystemName ); ///< See property eraseModeFilesystem Q_SIGNALS: void installChoiceChanged( InstallChoice ); void swapChoiceChanged( SwapChoice ); + void eraseModeFilesystemChanged( const QString& ); private: + /** @brief Handle FS-type configuration, for erase and default */ + void fillConfigurationFSTypes( const QVariantMap& configurationMap ); + EraseFsTypesSet m_eraseFsTypes; + QString m_eraseFsTypeChoice; + FileSystem::Type m_defaultFsType; + SwapChoiceSet m_swapChoices; SwapChoice m_initialSwapChoice = NoSwap; SwapChoice m_swapChoice = NoSwap; @@ -115,6 +155,8 @@ private: InstallChoice m_installChoice = NoChoice; qreal m_requiredStorageGiB = 0.0; // May duplicate setting in the welcome module QStringList m_requiredPartitionTableType; + + bool m_allowManualPartitioning = true; }; /** @brief Given a set of swap choices, return a sensible value from it. diff --git a/src/modules/partition/gui/PartitionViewStep.cpp b/src/modules/partition/PartitionViewStep.cpp similarity index 91% rename from src/modules/partition/gui/PartitionViewStep.cpp rename to src/modules/partition/PartitionViewStep.cpp index bbb865f30..fc6b3e50c 100644 --- a/src/modules/partition/gui/PartitionViewStep.cpp +++ b/src/modules/partition/PartitionViewStep.cpp @@ -11,10 +11,10 @@ * */ -#include "gui/PartitionViewStep.h" +#include "PartitionViewStep.h" +#include "Config.h" #include "core/BootLoaderModel.h" -#include "core/Config.h" #include "core/DeviceModel.h" #include "core/PartitionCoreModule.h" #include "gui/ChoicePage.h" @@ -327,7 +327,7 @@ PartitionViewStep::isNextEnabled() const void PartitionViewStep::nextPossiblyChanged( bool ) { - emit nextStatusChanged( isNextEnabled() ); + Q_EMIT nextStatusChanged( isNextEnabled() ); } bool @@ -368,7 +368,7 @@ PartitionViewStep::isAtEnd() const void PartitionViewStep::onActivate() { - m_config->updateGlobalStorage(); + m_config->fillGSSecondaryConfiguration(); // if we're coming back to PVS from the next VS if ( m_widget->currentWidget() == m_choicePage && m_config->installChoice() == Config::InstallChoice::Alongside ) @@ -541,32 +541,11 @@ PartitionViewStep::onLeave() void PartitionViewStep::setConfigurationMap( const QVariantMap& configurationMap ) { - Logger::Once o; - m_config->setConfigurationMap( configurationMap ); // Copy the efiSystemPartition setting to the global storage. It is needed not only in // the EraseDiskPage, but also in the bootloader configuration modules (grub, bootloader). Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage(); - QString efiSP = CalamaresUtils::getString( configurationMap, "efiSystemPartition", QStringLiteral( "/boot/efi" ) ); - gs->insert( "efiSystemPartition", efiSP ); - - // Set up firmwareType global storage entry. This is used, e.g. by the bootloader module. - QString firmwareType( PartUtils::isEfiSystem() ? QStringLiteral( "efi" ) : QStringLiteral( "bios" ) ); - cDebug() << o << "Setting firmwareType to" << firmwareType; - gs->insert( "firmwareType", firmwareType ); - - // Read and parse key efiSystemPartitionSize - if ( configurationMap.contains( "efiSystemPartitionSize" ) ) - { - gs->insert( "efiSystemPartitionSize", CalamaresUtils::getString( configurationMap, "efiSystemPartitionSize" ) ); - } - - // Read and parse key efiSystemPartitionName - if ( configurationMap.contains( "efiSystemPartitionName" ) ) - { - gs->insert( "efiSystemPartitionName", CalamaresUtils::getString( configurationMap, "efiSystemPartitionName" ) ); - } // Read and parse key swapPartitionName if ( configurationMap.contains( "swapPartitionName" ) ) @@ -582,30 +561,6 @@ PartitionViewStep::setConfigurationMap( const QVariantMap& configurationMap ) gs->insert( "enableLuksAutomatedPartitioning", CalamaresUtils::getBool( configurationMap, "enableLuksAutomatedPartitioning", true ) ); - // The defaultFileSystemType setting needs a bit more processing, - // as we want to cover various cases (such as different cases) - QString fsName = CalamaresUtils::getString( configurationMap, "defaultFileSystemType" ); - FileSystem::Type fsType; - if ( fsName.isEmpty() ) - { - cWarning() << "Partition-module setting *defaultFileSystemType* is missing, will use ext4"; - } - QString fsRealName = PartUtils::findFS( fsName, &fsType ); - if ( fsRealName == fsName ) - { - cDebug() << o << "Partition-module setting *defaultFileSystemType*" << fsRealName; - } - else if ( fsType != FileSystem::Unknown ) - { - cWarning() << "Partition-module setting *defaultFileSystemType* changed" << fsRealName; - } - else - { - cWarning() << "Partition-module setting *defaultFileSystemType* is bad (" << fsName << ") using" << fsRealName - << "instead."; - } - gs->insert( "defaultFileSystemType", fsRealName ); - QString partitionTableName = CalamaresUtils::getString( configurationMap, "defaultPartitionTableType" ); if ( partitionTableName.isEmpty() ) { @@ -627,7 +582,7 @@ PartitionViewStep::setConfigurationMap( const QVariantMap& configurationMap ) QFuture< void > future = QtConcurrent::run( this, &PartitionViewStep::initPartitionCoreModule ); m_future->setFuture( future ); - m_core->initLayout( fsType == FileSystem::Unknown ? FileSystem::Ext4 : fsType, + m_core->initLayout( m_config->defaultFsType(), configurationMap.value( "partitionLayout" ).toList() ); } diff --git a/src/modules/partition/gui/PartitionViewStep.h b/src/modules/partition/PartitionViewStep.h similarity index 100% rename from src/modules/partition/gui/PartitionViewStep.h rename to src/modules/partition/PartitionViewStep.h diff --git a/src/modules/partition/core/DeviceModel.cpp b/src/modules/partition/core/DeviceModel.cpp index 46a181b33..33aae20c0 100644 --- a/src/modules/partition/core/DeviceModel.cpp +++ b/src/modules/partition/core/DeviceModel.cpp @@ -132,7 +132,7 @@ DeviceModel::swapDevice( Device* oldDevice, Device* newDevice ) m_devices[ indexOfOldDevice ] = newDevice; - emit dataChanged( index( indexOfOldDevice ), index( indexOfOldDevice ) ); + Q_EMIT dataChanged( index( indexOfOldDevice ), index( indexOfOldDevice ) ); } void diff --git a/src/modules/partition/core/PartUtils.cpp b/src/modules/partition/core/PartUtils.cpp index 47654a2be..2f269d37f 100644 --- a/src/modules/partition/core/PartUtils.cpp +++ b/src/modules/partition/core/PartUtils.cpp @@ -22,6 +22,7 @@ #include "partition/PartitionQuery.h" #include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" +#include "utils/RAII.h" #include #include @@ -479,21 +480,19 @@ isEfiBootable( const Partition* candidate ) } QString -findFS( QString fsName, FileSystem::Type* fsType ) +canonicalFilesystemName( const QString& fsName, FileSystem::Type* fsType ) { - QStringList fsLanguage { QLatin1String( "C" ) }; // Required language list to turn off localization + cPointerSetter type( fsType ); if ( fsName.isEmpty() ) { - fsName = QStringLiteral( "ext4" ); + type = FileSystem::Ext4; + return QStringLiteral( "ext4" ); } - FileSystem::Type tmpType = FileSystem::typeForName( fsName, fsLanguage ); - if ( tmpType != FileSystem::Unknown ) + QStringList fsLanguage { QLatin1String( "C" ) }; // Required language list to turn off localization + + if ( ( type = FileSystem::typeForName( fsName, fsLanguage ) ) != FileSystem::Unknown ) { - if ( fsType ) - { - *fsType = tmpType; - } return fsName; } @@ -513,7 +512,6 @@ findFS( QString fsName, FileSystem::Type* fsType ) } cWarning() << "Filesystem" << fsName << "not found, using ext4"; - fsName = QStringLiteral( "ext4" ); // fsType can be used to check whether fsName was a valid filesystem. if ( fsType ) { @@ -533,7 +531,8 @@ findFS( QString fsName, FileSystem::Type* fsType ) } } #endif - return fsName; + type = FileSystem::Unknown; + return QStringLiteral( "ext4" ); } } // namespace PartUtils diff --git a/src/modules/partition/core/PartUtils.h b/src/modules/partition/core/PartUtils.h index 404dd5a77..5e84e379b 100644 --- a/src/modules/partition/core/PartUtils.h +++ b/src/modules/partition/core/PartUtils.h @@ -91,11 +91,13 @@ bool isEfiBootable( const Partition* candidate ); /** @brief translate @p fsName into a recognized name and type * * Makes several attempts to translate the string into a - * name that KPMCore will recognize. + * name that KPMCore will recognize. Returns the canonical + * filesystem name (e.g. asking for "EXT4" will return "ext4"). + * * The corresponding filesystem type is stored in @p fsType, and * its value is FileSystem::Unknown if @p fsName is not recognized. */ -QString findFS( QString fsName, FileSystem::Type* fsType ); +QString canonicalFilesystemName( const QString& fsName, FileSystem::Type* fsType ); } // namespace PartUtils diff --git a/src/modules/partition/core/PartitionActions.cpp b/src/modules/partition/core/PartitionActions.cpp index 1a3f6246b..422c1d38f 100644 --- a/src/modules/partition/core/PartitionActions.cpp +++ b/src/modules/partition/core/PartitionActions.cpp @@ -16,13 +16,12 @@ #include "core/PartitionCoreModule.h" #include "core/PartitionInfo.h" -#include "utils/CalamaresUtilsSystem.h" -#include "utils/NamedEnum.h" -#include "utils/Units.h" - #include "GlobalStorage.h" #include "JobQueue.h" +#include "utils/CalamaresUtilsSystem.h" #include "utils/Logger.h" +#include "utils/NamedEnum.h" +#include "utils/Units.h" #include #include @@ -109,6 +108,12 @@ doAutopartition( PartitionCoreModule* core, Device* dev, Choices::AutoPartitionO partType = isEfi ? PartitionTable::gpt : PartitionTable::msdos; } + // Looking up the defaultFsType (which should name a filesystem type) + // will log an error and set the type to Unknown if there's something wrong. + FileSystem::Type type = FileSystem::Unknown; + PartUtils::canonicalFilesystemName( o.defaultFsType, &type ); + core->initLayout( type == FileSystem::Unknown ? FileSystem::Ext4 : type ); + core->createPartitionTable( dev, partType ); if ( isEfi ) diff --git a/src/modules/partition/core/PartitionActions.h b/src/modules/partition/core/PartitionActions.h index 3a345dc4e..4a60f4735 100644 --- a/src/modules/partition/core/PartitionActions.h +++ b/src/modules/partition/core/PartitionActions.h @@ -10,7 +10,7 @@ #ifndef PARTITIONACTIONS_H #define PARTITIONACTIONS_H -#include "core/Config.h" +#include "Config.h" #include #include diff --git a/src/modules/partition/core/PartitionCoreModule.cpp b/src/modules/partition/core/PartitionCoreModule.cpp index 363d9daef..e2c91fbee 100644 --- a/src/modules/partition/core/PartitionCoreModule.cpp +++ b/src/modules/partition/core/PartitionCoreModule.cpp @@ -687,7 +687,7 @@ void PartitionCoreModule::refreshPartition( Device* device, Partition* ) { // Keep it simple for now: reset the model. This can be improved to cause - // the model to emit dataChanged() for the affected row instead, avoiding + // the model to Q_EMIT dataChanged() for the affected row instead, avoiding // the loss of the current selection. auto model = partitionModelForDevice( device ); Q_ASSERT( model ); @@ -966,7 +966,7 @@ PartitionCoreModule::revert() m_deviceInfos.clear(); doInit(); updateIsDirty(); - emit reverted(); + Q_EMIT reverted(); } @@ -1040,7 +1040,7 @@ PartitionCoreModule::revertDevice( Device* dev, bool individualRevert ) { refreshAfterModelChange(); } - emit deviceReverted( newDev ); + Q_EMIT deviceReverted( newDev ); } diff --git a/src/modules/partition/core/PartitionLayout.cpp b/src/modules/partition/core/PartitionLayout.cpp index 23eaeda62..a8ed206d8 100644 --- a/src/modules/partition/core/PartitionLayout.cpp +++ b/src/modules/partition/core/PartitionLayout.cpp @@ -75,7 +75,7 @@ PartitionLayout::PartitionEntry::PartitionEntry( const QString& label, , partMinSize( minSize ) , partMaxSize( maxSize ) { - PartUtils::findFS( fs, &partFileSystem ); + PartUtils::canonicalFilesystemName( fs, &partFileSystem ); } @@ -95,7 +95,7 @@ PartitionLayout::addEntry( const PartitionEntry& entry ) void PartitionLayout::init( FileSystem::Type defaultFsType, const QVariantList& config ) { - bool ok; + bool ok = true; // bogus argument to getSubMap() m_partLayout.clear(); @@ -130,10 +130,71 @@ PartitionLayout::init( FileSystem::Type defaultFsType, const QVariantList& confi if ( !m_partLayout.count() ) { - addEntry( { defaultFsType, QString( "/" ), QString( "100%" ) } ); + // Unknown will be translated to defaultFsType at apply-time + addEntry( { FileSystem::Type::Unknown, QString( "/" ), QString( "100%" ) } ); } + + setDefaultFsType( defaultFsType ); +} + +void +PartitionLayout::setDefaultFsType(FileSystem::Type defaultFsType) +{ + using T = FileSystem::Type; + switch ( defaultFsType ) + { + case T::Unknown: + case T::Unformatted: + case T::Extended: + case T::LinuxSwap: + case T::Luks: + case T::Ocfs2: + case T::Lvm2_PV: + case T::Udf: + case T::Iso9660: + case T::Luks2: + case T::LinuxRaidMember: + case T::BitLocker: + // bad bad + cWarning() << "The selected default FS" << defaultFsType << "is not suitable." << "Using ext4 instead."; + defaultFsType = T::Ext4; + break; + case T::Ext2: + case T::Ext3: + case T::Ext4: + case T::Fat32: + case T::Ntfs: + case T::Reiser4: + case T::ReiserFS: + case T::Xfs: + case T::Jfs: + case T::Btrfs: + case T::Exfat: + case T::F2fs: + // ok + break; + case T::Fat12: + case T::Fat16: + case T::Hfs: + case T::HfsPlus: + case T::Ufs: + case T::Hpfs: + case T::Zfs: + case T::Nilfs2: + case T::Apfs: + case T::Minix: + // weird + cWarning() << "The selected default FS" << defaultFsType << "is unusual, but not wrong."; + break; + default: + cWarning() << "The selected default FS" << defaultFsType << "is not known to Calamares." << "Using ext4 instead."; + defaultFsType = T::Ext4; + } + + m_defaultFsType = defaultFsType; } + QList< Partition* > PartitionLayout::createPartitions( Device* dev, qint64 firstSector, @@ -142,6 +203,9 @@ PartitionLayout::createPartitions( Device* dev, PartitionNode* parent, const PartitionRole& role ) { + // Make sure the default FS is sensible; warn and use ext4 if not + setDefaultFsType( m_defaultFsType ); + QList< Partition* > partList; // Map each partition entry to its requested size (0 when calculated later) QMap< const PartitionLayout::PartitionEntry*, qint64 > partSectorsMap; @@ -210,6 +274,8 @@ PartitionLayout::createPartitions( Device* dev, } } + auto correctFS = [d=m_defaultFsType]( FileSystem::Type t ) { return t == FileSystem::Type::Unknown ? d : t; }; + // Create the partitions. currentSector = firstSector; availableSectors = totalSectors; @@ -229,7 +295,7 @@ PartitionLayout::createPartitions( Device* dev, part = KPMHelpers::createNewPartition( parent, *dev, role, - entry.partFileSystem, + correctFS( entry.partFileSystem ), entry.partLabel, currentSector, currentSector + sectors - 1, @@ -240,7 +306,7 @@ PartitionLayout::createPartitions( Device* dev, part = KPMHelpers::createNewEncryptedPartition( parent, *dev, role, - entry.partFileSystem, + correctFS( entry.partFileSystem ), entry.partLabel, currentSector, currentSector + sectors - 1, diff --git a/src/modules/partition/core/PartitionLayout.h b/src/modules/partition/core/PartitionLayout.h index 6e0c73f8f..c4c26d9f1 100644 --- a/src/modules/partition/core/PartitionLayout.h +++ b/src/modules/partition/core/PartitionLayout.h @@ -87,11 +87,28 @@ public: * * @p config is a list of partition entries (in QVariant form, * read from YAML). If no entries are given, then a single - * partition is created with the given @p defaultFsType + * partition is created with type Unkown. + * + * Any partitions with FS type Unknown will get the default filesystem + * that is set at **apply** time (e.g. when createPartitions() is + * called as well. + * + * @see setDefaultFsType() */ void init( FileSystem::Type defaultFsType, const QVariantList& config ); + /** @brief add an entry as if it had been listed in the config + * + * The same comments about filesystem type apply. + */ bool addEntry( const PartitionEntry& entry ); + /** @brief set the default filesystem type + * + * Any partitions in the layout with type Unknown will get + * the default type when createPartitions() is called. + */ + void setDefaultFsType( FileSystem::Type defaultFsType ); + /** * @brief Apply the current partition layout to the selected drive space. * @return A list of Partition objects. @@ -105,6 +122,7 @@ public: private: QList< PartitionEntry > m_partLayout; + FileSystem::Type m_defaultFsType = FileSystem::Type::Unknown; }; #endif /* PARTITIONLAYOUT_H */ diff --git a/src/modules/partition/core/PartitionModel.cpp b/src/modules/partition/core/PartitionModel.cpp index b4ea57adb..e310eee5e 100644 --- a/src/modules/partition/core/PartitionModel.cpp +++ b/src/modules/partition/core/PartitionModel.cpp @@ -327,5 +327,5 @@ PartitionModel::partitionForIndex( const QModelIndex& index ) const void PartitionModel::update() { - emit dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) ); + Q_EMIT dataChanged( index( 0, 0 ), index( rowCount() - 1, columnCount() - 1 ) ); } diff --git a/src/modules/partition/core/PartitionModel.h b/src/modules/partition/core/PartitionModel.h index 433fa0620..ba5e258a3 100644 --- a/src/modules/partition/core/PartitionModel.h +++ b/src/modules/partition/core/PartitionModel.h @@ -41,7 +41,7 @@ public: /** * This helper class must be instantiated on the stack *before* making * changes to the device represented by this model. It will cause the model - * to emit modelAboutToBeReset() when instantiated and modelReset() when + * to Q_EMIT modelAboutToBeReset() when instantiated and modelReset() when * destructed. */ class ResetHelper diff --git a/src/modules/partition/gui/ChoicePage.cpp b/src/modules/partition/gui/ChoicePage.cpp index d2f5c99e7..6438724e8 100644 --- a/src/modules/partition/gui/ChoicePage.cpp +++ b/src/modules/partition/gui/ChoicePage.cpp @@ -11,15 +11,9 @@ #include "ChoicePage.h" -#include "BootInfoWidget.h" -#include "DeviceInfoWidget.h" -#include "PartitionBarsView.h" -#include "PartitionLabelsView.h" -#include "PartitionSplitterWidget.h" -#include "ReplaceWidget.h" -#include "ScanningDialog.h" +#include "Config.h" + #include "core/BootLoaderModel.h" -#include "core/Config.h" #include "core/DeviceModel.h" #include "core/KPMHelpers.h" #include "core/OsproberEntry.h" @@ -28,6 +22,13 @@ #include "core/PartitionCoreModule.h" #include "core/PartitionInfo.h" #include "core/PartitionModel.h" +#include "gui/BootInfoWidget.h" +#include "gui/DeviceInfoWidget.h" +#include "gui/PartitionBarsView.h" +#include "gui/PartitionLabelsView.h" +#include "gui/PartitionSplitterWidget.h" +#include "gui/ReplaceWidget.h" +#include "gui/ScanningDialog.h" #include "Branding.h" #include "GlobalStorage.h" @@ -269,6 +270,15 @@ ChoicePage::setupChoices() m_eraseButton->addOptionsComboBox( m_eraseSwapChoiceComboBox ); } + if ( m_config->eraseFsTypes().count() > 1) + { + m_eraseFsTypesChoiceComboBox = new QComboBox; + m_eraseFsTypesChoiceComboBox->addItems(m_config->eraseFsTypes()); + connect( m_eraseFsTypesChoiceComboBox, &QComboBox::currentTextChanged, m_config, &Config::setEraseFsTypeChoice ); + connect( m_config, &Config::eraseModeFilesystemChanged, this, &ChoicePage::onActionChanged ); + m_eraseButton->addOptionsComboBox( m_eraseFsTypesChoiceComboBox ); + } + m_itemsLayout->addWidget( m_alongsideButton ); m_itemsLayout->addWidget( m_replaceButton ); m_itemsLayout->addWidget( m_eraseButton ); @@ -293,7 +303,7 @@ ChoicePage::setupChoices() m_config->setInstallChoice( id ); updateNextEnabled(); - emit actionChosen(); + Q_EMIT actionChosen(); } else // An action was unpicked, either on its own or because of another selection. { @@ -303,7 +313,7 @@ ChoicePage::setupChoices() m_config->setInstallChoice( InstallChoice::NoChoice ); updateNextEnabled(); - emit actionChosen(); + Q_EMIT actionChosen(); } } } ); @@ -426,8 +436,8 @@ ChoicePage::continueApplyDeviceChoice() checkInstallChoiceRadioButton( m_config->installChoice() ); } - emit actionChosen(); - emit deviceChosen(); + Q_EMIT actionChosen(); + Q_EMIT deviceChosen(); } @@ -465,9 +475,8 @@ ChoicePage::applyActionChoice( InstallChoice choice ) case InstallChoice::Erase: { auto gs = Calamares::JobQueue::instance()->globalStorage(); - PartitionActions::Choices::AutoPartitionOptions options { gs->value( "defaultPartitionTableType" ).toString(), - gs->value( "defaultFileSystemType" ).toString(), + m_config->eraseFsType(), m_encryptWidget->passphrase(), gs->value( "efiSystemPartition" ).toString(), CalamaresUtils::GiBtoBytes( @@ -483,14 +492,14 @@ ChoicePage::applyActionChoice( InstallChoice choice ) } ), [=] { PartitionActions::doAutopartition( m_core, selectedDevice(), options ); - emit deviceChosen(); + Q_EMIT deviceChosen(); }, this ); } else { PartitionActions::doAutopartition( m_core, selectedDevice(), options ); - emit deviceChosen(); + Q_EMIT deviceChosen(); } } break; @@ -1595,7 +1604,7 @@ ChoicePage::updateNextEnabled() if ( enabled != m_nextEnabled ) { m_nextEnabled = enabled; - emit nextStatusChanged( enabled ); + Q_EMIT nextStatusChanged( enabled ); } } diff --git a/src/modules/partition/gui/ChoicePage.h b/src/modules/partition/gui/ChoicePage.h index 89bd775d7..5e0a24d43 100644 --- a/src/modules/partition/gui/ChoicePage.h +++ b/src/modules/partition/gui/ChoicePage.h @@ -15,7 +15,7 @@ #include "ui_ChoicePage.h" -#include "core/Config.h" +#include "Config.h" #include "core/OsproberEntry.h" #include @@ -138,7 +138,9 @@ private: Calamares::PrettyRadioButton* m_eraseButton; Calamares::PrettyRadioButton* m_replaceButton; Calamares::PrettyRadioButton* m_somethingElseButton; - QComboBox* m_eraseSwapChoiceComboBox; // UI, see also m_eraseSwapChoice + QComboBox* m_eraseSwapChoiceComboBox = nullptr; // UI, see also Config's swap choice + QComboBox* m_eraseFsTypesChoiceComboBox = nullptr; // UI, see also Config's erase-mode FS + DeviceInfoWidget* m_deviceInfoWidget; diff --git a/src/modules/partition/gui/CreatePartitionDialog.cpp b/src/modules/partition/gui/CreatePartitionDialog.cpp index 5ebe15336..cdc9992b9 100644 --- a/src/modules/partition/gui/CreatePartitionDialog.cpp +++ b/src/modules/partition/gui/CreatePartitionDialog.cpp @@ -92,7 +92,7 @@ CreatePartitionDialog::CreatePartitionDialog( Device* device, // File system; the config value is translated (best-effort) to a type FileSystem::Type defaultFSType; - QString untranslatedFSName = PartUtils::findFS( + QString untranslatedFSName = PartUtils::canonicalFilesystemName( Calamares::JobQueue::instance()->globalStorage()->value( "defaultFileSystemType" ).toString(), &defaultFSType ); if ( defaultFSType == FileSystem::Type::Unknown ) { diff --git a/src/modules/partition/gui/EditExistingPartitionDialog.cpp b/src/modules/partition/gui/EditExistingPartitionDialog.cpp index e2d7ac5ba..411d6d0dc 100644 --- a/src/modules/partition/gui/EditExistingPartitionDialog.cpp +++ b/src/modules/partition/gui/EditExistingPartitionDialog.cpp @@ -97,7 +97,7 @@ EditExistingPartitionDialog::EditExistingPartitionDialog( Device* device, m_ui->fileSystemComboBox->addItems( fsNames ); FileSystem::Type defaultFSType; - QString untranslatedFSName = PartUtils::findFS( + QString untranslatedFSName = PartUtils::canonicalFilesystemName( Calamares::JobQueue::instance()->globalStorage()->value( "defaultFileSystemType" ).toString(), &defaultFSType ); if ( defaultFSType == FileSystem::Type::Unknown ) { diff --git a/src/modules/partition/gui/EncryptWidget.cpp b/src/modules/partition/gui/EncryptWidget.cpp index 7f648491a..ee50e7d66 100644 --- a/src/modules/partition/gui/EncryptWidget.cpp +++ b/src/modules/partition/gui/EncryptWidget.cpp @@ -136,7 +136,7 @@ EncryptWidget::updateState() if ( newState != m_state ) { m_state = newState; - emit stateChanged( m_state ); + Q_EMIT stateChanged( m_state ); } } diff --git a/src/modules/partition/gui/PartitionSplitterWidget.cpp b/src/modules/partition/gui/PartitionSplitterWidget.cpp index 93c77bb69..0cafe7814 100644 --- a/src/modules/partition/gui/PartitionSplitterWidget.cpp +++ b/src/modules/partition/gui/PartitionSplitterWidget.cpp @@ -225,7 +225,7 @@ PartitionSplitterWidget::setSplitPartition( const QString& path, qint64 minSize, } } - emit partitionResized( m_itemToResize.itemPath, m_itemToResize.size, m_itemToResizeNext.size ); + Q_EMIT partitionResized( m_itemToResize.itemPath, m_itemToResize.size, m_itemToResizeNext.size ); cDebug() << "Items updated. Status:"; foreach ( const PartitionSplitterItem& item, m_items ) @@ -374,7 +374,7 @@ PartitionSplitterWidget::mouseMoveEvent( QMouseEvent* event ) repaint(); - emit partitionResized( itemPath, m_itemToResize.size, m_itemToResizeNext.size ); + Q_EMIT partitionResized( itemPath, m_itemToResize.size, m_itemToResizeNext.size ); } else { diff --git a/src/modules/partition/gui/ReplaceWidget.cpp b/src/modules/partition/gui/ReplaceWidget.cpp index 078eb4de7..94f527646 100644 --- a/src/modules/partition/gui/ReplaceWidget.cpp +++ b/src/modules/partition/gui/ReplaceWidget.cpp @@ -311,7 +311,7 @@ ReplaceWidget::setNextEnabled( bool enabled ) } m_nextEnabled = enabled; - emit nextStatusChanged( enabled ); + Q_EMIT nextStatusChanged( enabled ); } diff --git a/src/modules/partition/gui/ScanningDialog.cpp b/src/modules/partition/gui/ScanningDialog.cpp index cd22bb861..4dffa922b 100644 --- a/src/modules/partition/gui/ScanningDialog.cpp +++ b/src/modules/partition/gui/ScanningDialog.cpp @@ -68,5 +68,5 @@ void ScanningDialog::setVisible( bool visible ) { QDialog::setVisible( visible ); - emit visibilityChanged(); + Q_EMIT visibilityChanged(); } diff --git a/src/modules/partition/jobs/PartitionJob.cpp b/src/modules/partition/jobs/PartitionJob.cpp index 3bdb05ebd..ca9b00944 100644 --- a/src/modules/partition/jobs/PartitionJob.cpp +++ b/src/modules/partition/jobs/PartitionJob.cpp @@ -25,5 +25,5 @@ PartitionJob::iprogress( int percent ) { percent = 100; } - emit progress( qreal( percent / 100.0 ) ); + Q_EMIT progress( qreal( percent / 100.0 ) ); } diff --git a/src/modules/partition/partition.conf b/src/modules/partition/partition.conf index 7330f92b3..3213e0d33 100644 --- a/src/modules/partition/partition.conf +++ b/src/modules/partition/partition.conf @@ -113,12 +113,11 @@ initialSwapChoice: none # Restrict the installation on disks that match the type of partition # tables that are specified. # -# Suggested values: msdos, gpt -# If nothing is specified, Calamares defaults to both "msdos" and "mbr". +# Possible values: msdos, gpt. Names are case-sensitive and defined by KPMCore. +# +# If nothing is specified, Calamares defaults to both "msdos" and "gpt". # -# Names are case-sensitive and defined by KPMCore. # requiredPartitionTableType: gpt -# or, # requiredPartitionTableType: # - msdos # - gpt @@ -139,6 +138,17 @@ initialSwapChoice: none # Names are case-sensitive and defined by KPMCore. defaultFileSystemType: "ext4" +# Selectable filesystem type, used when "erase" is done. +# +# When erasing the disk, the *defaultFileSystemType* is used (see +# above), but it is also possible to give users a choice: +# list suitable filesystems here. A drop-down is provided +# to pick which is the filesystems will be used. +# +# The value *defaultFileSystemType* is added to this list (with a warning) +# if not present; the default pick is the *defaultFileSystemType*. +availableFileSystemTypes: ["ext4", "btrfs", "f2fs"] + # Show/hide LUKS related functionality in automated partitioning modes. # Disable this if you choose not to deploy early unlocking support in GRUB2 # and/or your distribution's initramfs solution. @@ -205,7 +215,12 @@ defaultFileSystemType: "ext4" # - uuid: partition uuid (optional parameter; gpt only; requires KPMCore >= 4.2.0) # - type: partition type (optional parameter; gpt only; requires KPMCore >= 4.2.0) # - attributes: partition attributes (optional parameter; gpt only; requires KPMCore >= 4.2.0) -# - filesystem: filesystem type (optional parameter; fs not created if "unformatted" or unset) +# - filesystem: filesystem type (optional parameter) +# - if not set at all, treat as "unformatted" +# - if "unformatted", no filesystem will be created +# - if "unknown" (or an unknown FS name, like "elephant") then the +# default filesystem type, or the user's choice, will be applied instead +# of "unknown" (e.g. the user might pick ext4, or xfs). # - mountPoint: partition mount point (optional parameter; not mounted if unset) # - size: partition size in bytes (append 'K', 'M' or 'G' for KiB, MiB or GiB) # or diff --git a/src/modules/partition/partition.schema.yaml b/src/modules/partition/partition.schema.yaml index 16cc08319..6c65e8ae7 100644 --- a/src/modules/partition/partition.schema.yaml +++ b/src/modules/partition/partition.schema.yaml @@ -18,6 +18,8 @@ properties: alwaysShowPartitionLabels: { type: boolean, default: true } defaultFileSystemType: { type: string } + availableFileSystemTypes: { type: array, items: { type: string } } + enableLuksAutomatedPartitioning: { type: boolean, default: false } allowManualPartitioning: { type: boolean, default: true } partitionLayout: { type: array } # TODO: specify items