diff --git a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go index 9c1f746..70d8d5e 100644 --- a/webapp/backend/pkg/database/migrations/m20220716214900/setting.go +++ b/webapp/backend/pkg/database/migrations/m20220716214900/setting.go @@ -14,4 +14,5 @@ type Setting struct { SettingValueNumeric int `json:"setting_value_numeric"` SettingValueString string `json:"setting_value_string"` + SettingValueBool bool `json:"setting_value_bool"` } diff --git a/webapp/backend/pkg/database/scrutiny_repository.go b/webapp/backend/pkg/database/scrutiny_repository.go index 81f2316..521ba7d 100644 --- a/webapp/backend/pkg/database/scrutiny_repository.go +++ b/webapp/backend/pkg/database/scrutiny_repository.go @@ -62,7 +62,20 @@ func NewScrutinyRepository(appConfig config.Interface, globalLogger logrus.Field // Gorm/SQLite setup //////////////////////////////////////////////////////////////////////////////////////////////////////////////////// globalLogger.Infof("Trying to connect to scrutiny sqlite db: %s\n", appConfig.GetString("web.database.location")) - database, err := gorm.Open(sqlite.Open(appConfig.GetString("web.database.location")), &gorm.Config{ + + // When a transaction cannot lock the database, because it is already locked by another one, + // SQLite by default throws an error: database is locked. This behavior is usually not appropriate when + // concurrent access is needed, typically when multiple processes write to the same database. + // PRAGMA busy_timeout lets you set a timeout or a handler for these events. When setting a timeout, + // SQLite will try the transaction multiple times within this timeout. + // fixes #341 + // https://rsqlite.r-dbi.org/reference/sqlitesetbusyhandler + // retrying for 30000 milliseconds, 30seconds - this would be unreasonable for a distributed multi-tenant application, + // but should be fine for local usage. + pragmaStr := sqlitePragmaString(map[string]string{ + "busy_timeout": "30000", + }) + database, err := gorm.Open(sqlite.Open(appConfig.GetString("web.database.location")+pragmaStr), &gorm.Config{ //TODO: figure out how to log database queries again. //Logger: logger DisableForeignKeyConstraintWhenMigrating: true, @@ -450,3 +463,16 @@ func (sr *scrutinyRepository) lookupNestedDurationKeys(durationKey string) []str } return []string{DURATION_KEY_WEEK} } + +func sqlitePragmaString(pragmas map[string]string) string { + q := url.Values{} + for key, val := range pragmas { + q.Add("_pragma", key+"="+val) + } + + queryStr := q.Encode() + if len(queryStr) > 0 { + return "?" + queryStr + } + return "" +} diff --git a/webapp/backend/pkg/database/scrutiny_repository_migrations.go b/webapp/backend/pkg/database/scrutiny_repository_migrations.go index a6f1b68..015428c 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_migrations.go +++ b/webapp/backend/pkg/database/scrutiny_repository_migrations.go @@ -319,6 +319,12 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { SettingDataType: "string", SettingValueString: "celsius", }, + { + SettingKeyName: "file_size_si_units", + SettingKeyDescription: "File size in SI units (true | false)", + SettingDataType: "bool", + SettingValueBool: false, + }, { SettingKeyName: "metrics.notify_level", @@ -349,6 +355,30 @@ func (sr *scrutinyRepository) Migrate(ctx context.Context) error { return err } sr.logger.Infoln("Database migration completed successfully") + + //these migrations cannot be done within a transaction, so they are done as a separate group, with `UseTransaction = false` + sr.logger.Infoln("SQLite global configuration migrations starting. Please wait....") + globalMigrateOptions := gormigrate.DefaultOptions + globalMigrateOptions.UseTransaction = false + gm := gormigrate.New(sr.gormClient, globalMigrateOptions, []*gormigrate.Migration{ + { + ID: "g20220802211500", + Migrate: func(tx *gorm.DB) error { + //shrink the Database (maybe necessary after 20220503113100) + if err := tx.Exec("VACUUM;").Error; err != nil { + return err + } + return nil + }, + }, + }) + + if err := gm.Migrate(); err != nil { + sr.logger.Errorf("SQLite global configuration migrations failed with error. \n Please open a github issue at https://github.com/AnalogJ/scrutiny and attach a copy of your scrutiny.db file. \n %v", err) + return err + } + sr.logger.Infoln("SQLite global configuration migrations completed successfully") + return nil } diff --git a/webapp/backend/pkg/database/scrutiny_repository_settings.go b/webapp/backend/pkg/database/scrutiny_repository_settings.go index 918a9f4..d92ce9b 100644 --- a/webapp/backend/pkg/database/scrutiny_repository_settings.go +++ b/webapp/backend/pkg/database/scrutiny_repository_settings.go @@ -24,6 +24,8 @@ func (sr *scrutinyRepository) LoadSettings(ctx context.Context) (*models.Setting sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueNumeric) } else if settingsEntry.SettingDataType == "string" { sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueString) + } else if settingsEntry.SettingDataType == "bool" { + sr.appConfig.SetDefault(configKey, settingsEntry.SettingValueBool) } } @@ -67,11 +69,13 @@ func (sr *scrutinyRepository) SaveSettings(ctx context.Context, settings models. settingsEntries[ndx].SettingValueNumeric = sr.appConfig.GetInt(configKey) } else if settingsEntry.SettingDataType == "string" { settingsEntries[ndx].SettingValueString = sr.appConfig.GetString(configKey) + } else if settingsEntry.SettingDataType == "bool" { + settingsEntries[ndx].SettingValueBool = sr.appConfig.GetBool(configKey) } // store in database. //TODO: this should be `sr.gormClient.Updates(&settingsEntries).Error` - err := sr.gormClient.Model(&models.SettingEntry{}).Where([]uint{settingsEntry.ID}).Select("setting_value_numeric", "setting_value_string").Updates(settingsEntries[ndx]).Error + err := sr.gormClient.Model(&models.SettingEntry{}).Where([]uint{settingsEntry.ID}).Select("setting_value_numeric", "setting_value_string", "setting_value_bool").Updates(settingsEntries[ndx]).Error if err != nil { return err } diff --git a/webapp/backend/pkg/models/setting_entry.go b/webapp/backend/pkg/models/setting_entry.go index 48d2c4c..2ac78f6 100644 --- a/webapp/backend/pkg/models/setting_entry.go +++ b/webapp/backend/pkg/models/setting_entry.go @@ -15,6 +15,7 @@ type SettingEntry struct { SettingValueNumeric int `json:"setting_value_numeric"` SettingValueString string `json:"setting_value_string"` + SettingValueBool bool `json:"setting_value_bool"` } func (s SettingEntry) TableName() string { diff --git a/webapp/backend/pkg/models/settings.go b/webapp/backend/pkg/models/settings.go index 48ba2d5..f06db84 100644 --- a/webapp/backend/pkg/models/settings.go +++ b/webapp/backend/pkg/models/settings.go @@ -13,6 +13,7 @@ type Settings struct { DashboardDisplay string `json:"dashboard_display" mapstructure:"dashboard_display"` DashboardSort string `json:"dashboard_sort" mapstructure:"dashboard_sort"` TemperatureUnit string `json:"temperature_unit" mapstructure:"temperature_unit"` + FileSizeSIUnits bool `json:"file_size_si_units" mapstructure:"file_size_si_units"` Metrics struct { NotifyLevel int `json:"notify_level" mapstructure:"notify_level"` diff --git a/webapp/frontend/src/app/core/config/app.config.ts b/webapp/frontend/src/app/core/config/app.config.ts index b4a6114..92f0451 100644 --- a/webapp/frontend/src/app/core/config/app.config.ts +++ b/webapp/frontend/src/app/core/config/app.config.ts @@ -43,6 +43,8 @@ export interface AppConfig { temperature_unit?: TemperatureUnit; + file_size_si_units?: boolean; + // Settings from Scrutiny API metrics?: { @@ -69,6 +71,8 @@ export const appConfig: AppConfig = { dashboard_sort: 'status', temperature_unit: 'celsius', + file_size_si_units: false, + metrics: { notify_level: MetricsNotifyLevel.Fail, status_filter_attributes: MetricsStatusFilterAttributes.All, diff --git a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html index 7dfe40c..43e8964 100644 --- a/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html +++ b/webapp/frontend/src/app/layout/common/dashboard-device/dashboard-device.component.html @@ -58,7 +58,8 @@