M harmony_changelog.md => harmony_changelog.md +1 -0
@@ 9,6 9,7 @@
* Added new 32px and 170px fonts
* Added new clock face with quotes
* Added a backend for quotes on home screen
+* Added progress bar to update process
### Changed / Improved
* Updated FSL drivers from NXP
M module-services/service-desktop/include/service-desktop/Constants.hpp => module-services/service-desktop/include/service-desktop/Constants.hpp +1 -2
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2023, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 11,5 11,4 @@ namespace sdesktop::paths
inline constexpr auto syncFilename = "sync.tar";
inline constexpr auto backupFilename = "backup.tar";
inline constexpr auto recoveryStatusFilename = "recovery_status.json";
-
} // namespace sdesktop::paths
M products/BellHybrid/BinaryAssetsVersions.cmake => products/BellHybrid/BinaryAssetsVersions.cmake +1 -1
@@ 1,6 1,6 @@
# This file sets versions of downloaded binaries for release packaging purposes
-if( NOT DEFINED ECOBOOT_BIN_VERSION)
+if (NOT DEFINED ECOBOOT_BIN_VERSION)
set(ECOBOOT_BIN_VERSION 2.0.8 CACHE STRING "bootloader binary version to download from bootloader release page")
endif()
M scripts/lua/install.sh => scripts/lua/install.sh +3 -3
@@ 1,5 1,5 @@
#!/usr/bin/env bash
-# Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
+# Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
# For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
function validate_product_selection() {
@@ 28,10 28,10 @@ if validate_product_selection; then
cd "$parent_path"
mkdir -p ${DESTINATION}
+ cp products/${PRODUCT}/scripts/*.lua ${DESTINATION}
cp -r products/${PRODUCT}/assets ${DESTINATION}/assets/
cp -r share ${DESTINATION}/share
cp -r migration/migration.lua ${DESTINATION}/share
- cp *.lua ${DESTINATION}
rm ${DESTINATION}/update_udm.lua
cd -
-fi>
\ No newline at end of file
+fi
A scripts/lua/products/BellHybrid/scripts/backup.lua => scripts/lua/products/BellHybrid/scripts/backup.lua +51 -0
@@ 0,0 1,51 @@
+local helpers = require('helpers')
+local recovery = require('recovery')
+local paths = require('paths')
+local tar = require('ltar')
+local lfs = require('lfs')
+local backup = {}
+
+-- Match only files with '.db' extensions and omit such files inside subdirectories
+local match_db_files = '^[^%/]*%.db$'
+
+backup.script_name = "backup.lua"
+backup.img_in_progress = "assets/gui_image_backup_in_progress.bin"
+backup.img_success = "assets/gui_image_backup_success.bin"
+backup.img_failure = "assets/gui_image_backup_failed.bin"
+backup.img_in_progress_wait = true
+
+local function check_available_space()
+ local db_size = helpers.dir_size_filtered(paths.db_dir, match_db_files)
+ local version_size = lfs.attributes(paths.version_file, 'size')
+ local available_space = recovery.sys.free_space(recovery.sys.user())
+ -- Multiply the result by two due to the internal padding inside tar
+ local required_space = (db_size + version_size) * 2
+
+ print(string.format("Checking disk space:\nNeeded space: %d bytes, available space: %d bytes", required_space,
+ available_space))
+
+ assert(available_space > required_space, "Not enough free space on user disk")
+end
+
+local function create_temp_dir()
+ if not helpers.exists(paths.temp_dir) then
+ print(string.format("'%s' does not exist, creating", paths.temp_dir))
+ lfs.mkdir(paths.temp_dir)
+ end
+end
+
+local function create_backup_package()
+ print(string.format("Creating backup package from the contents of '%s' directory and saving it into '%s'",
+ paths.db_dir, paths.backup_file))
+ tar.create_from_path_regex(paths.db_dir, paths.backup_file, match_db_files)
+ tar.append_file(paths.version_file, paths.backup_file)
+end
+
+function backup.execute()
+ check_available_space()
+ create_temp_dir()
+ create_backup_package()
+end
+
+return backup
+
A scripts/lua/products/BellHybrid/scripts/entry.lua => scripts/lua/products/BellHybrid/scripts/entry.lua +88 -0
@@ 0,0 1,88 @@
+local helpers = require('helpers')
+local paths = require('paths')
+local rec = require('recovery')
+local backup = require('backup')
+local restore = require('restore')
+local update = require('update')
+local factory = require('factory')
+
+local scripts = {}
+scripts[rec.sys.boot_reason_codes.backup] = backup
+scripts[rec.sys.boot_reason_codes.restore] = restore
+scripts[rec.sys.boot_reason_codes.update] = update
+scripts[rec.sys.boot_reason_codes.factory] = factory
+
+--- Display image
+-- @function display_image
+-- @param path path to raw image data
+-- @param should_wait whether to wait after showing the image
+local function display_image(path, should_wait)
+ local raw_data = helpers.read_whole_file(path)
+ rec.gui.clear()
+ rec.gui.display_raw_img(600, 480, raw_data)
+ -- Give some time to user to view the displayed image
+ if should_wait then
+ rec.sys.sleep(5)
+ end
+end
+
+local function print_recovery_info()
+ print(string.format("PureRecovery version: %s, branch: %s, revision: %s", rec.version(), rec.branch(),
+ rec.revision()))
+end
+
+local function print_boot_reason()
+ print(string.format("Boot reason: %s", rec.sys.boot_reason_str()))
+end
+
+local function generate_report_file(boot_reason_str, success, message)
+ local file_path = paths.temp_dir .. "/recovery_status.json"
+ local body = string.format(
+ "{\"version\": \"%s\",\"branch\": \"%s\",\"revision\": \"%s\",\"operation\": \"%s\",\"successful\": %s,\"message\": \"%s\"}",
+ rec.version(), rec.branch(), rec.revision(), boot_reason_str, tostring(success), message)
+ local fd = assert(io.open(file_path, 'w'))
+ fd:write(body)
+ fd:close()
+end
+
+local function handle_boot_reason()
+ local boot_reason = rec.sys.boot_reason()
+ local boot_reason_str = rec.sys.boot_reason_str()
+ rec.sys.set_boot_reason(rec.sys.boot_reason_codes.os)
+ return boot_reason, boot_reason_str
+end
+
+local function handle_script_success(boot_reason)
+ display_image(scripts[boot_reason].img_success, true)
+ print("Finished successfully")
+end
+
+local function handle_script_failure(boot_reason, message)
+ display_image(scripts[boot_reason].img_failure, true)
+ print(message)
+end
+
+local function handle_exit(boot_reason_str, status, message)
+ generate_report_file(boot_reason_str, status, message)
+ rec.gui.clear()
+end
+
+local function invoke_script(boot_reason)
+ print(string.format("Executing '%s' script...", scripts[boot_reason].script_name))
+ display_image(scripts[boot_reason].img_in_progress, scripts[boot_reason].img_in_progress_wait)
+
+ local status, message = pcall(scripts[boot_reason].execute)
+ if status then
+ handle_script_success(boot_reason)
+ return true, "ok"
+ else
+ handle_script_failure(boot_reason, message)
+ return false, message
+ end
+end
+
+print_recovery_info()
+print_boot_reason()
+local boot_reason, boot_reason_str = handle_boot_reason()
+local status, message = invoke_script(boot_reason)
+handle_exit(boot_reason_str, status, message)
R scripts/lua/factory.lua => scripts/lua/products/BellHybrid/scripts/factory.lua +1 -0
@@ 7,6 7,7 @@ factory.script_name = "factory.lua"
factory.img_in_progress = "assets/gui_image_factory_reset_in_progress.bin"
factory.img_success = "assets/gui_image_factory_reset_success.bin"
factory.img_failure = "assets/gui_image_factory_reset_failed.bin"
+factory.img_in_progress_wait = true
local function remove_old_databases()
print(string.format("Removing old databases from '%s'", paths.db_dir))
A scripts/lua/products/BellHybrid/scripts/restore.lua => scripts/lua/products/BellHybrid/scripts/restore.lua +115 -0
@@ 0,0 1,115 @@
+local helpers = require('helpers')
+local recovery = require('recovery')
+local paths = require('paths')
+local consts = require('consts')
+local ltar = require('ltar')
+local lfs = require('lfs')
+local json = require('lunajson')
+local migration = require('migration')
+local restore = {}
+
+local unpacked_backup_dir = paths.temp_dir .. "/backup"
+
+restore.script_name = "restore.lua"
+restore.img_in_progress = "assets/gui_image_restore_in_progress.bin"
+restore.img_success = "assets/gui_image_restore_success.bin"
+restore.img_failure = "assets/gui_image_restore_failed.bin"
+restore.img_in_progress_wait = true
+
+local function check_available_space()
+ local backup_size = lfs.attributes(paths.backup_file, 'size')
+ local available_space = recovery.sys.free_space(recovery.sys.user())
+
+ print(string.format("Checking disk space:\nNeeded space: %d bytes, available space: %d bytes", backup_size,
+ available_space))
+
+ assert(available_space > backup_size, "Not enough free space on user disk")
+end
+
+local function unpack_backup()
+ if helpers.exists(unpacked_backup_dir) then
+ print(string.format("Directory '%s' exists, removing", unpacked_backup_dir))
+ helpers.rmdir(unpacked_backup_dir)
+ end
+ lfs.mkdir(unpacked_backup_dir)
+ print(string.format("Unpacking backup file '%s' to '%s'", paths.backup_file, unpacked_backup_dir))
+ ltar.unpack(paths.backup_file, unpacked_backup_dir)
+end
+
+local function get_db_array_from_file(file)
+ local contents = helpers.read_whole_file(file)
+ local root = json.decode(contents)
+ local set = {}
+ for _, entry in pairs(root.databases) do
+ set[entry.name] = tonumber(entry.version)
+ end
+ return set
+end
+
+local function get_db_array_from_path(path)
+ local set = {}
+ for file in lfs.dir(path) do
+ local file_path = path .. "/" .. file
+ if file ~= "." and file ~= ".." then
+ if lfs.attributes(file_path, "mode") == "file" then
+ set[helpers.strip_from_extension(file)] = true;
+ end
+ end
+ end
+ return set
+end
+
+local function build_db_set()
+ local system_db_set = get_db_array_from_file(paths.version_file)
+ local backup_db_set = get_db_array_from_path(unpacked_backup_dir)
+ local set = {}
+ for name, version in pairs(system_db_set) do
+ if backup_db_set[name] then
+ set[name] = tonumber(version)
+ end
+ end
+ return set
+end
+
+local function perform_db_migration()
+ print("Performing database migration")
+
+ local result = migration.migrate(unpacked_backup_dir, paths.migration_scripts_dir, build_db_set())
+ assert(result == migration.retcode.OK, string.format("Database migration process failed with %d", result))
+end
+
+local function sync_databases()
+ print("Syncing databases:")
+
+ for name, _ in pairs(build_db_set()) do
+ local destination = paths.db_dir .. "/" .. name .. ".db"
+ local source = unpacked_backup_dir .. "/" .. name .. ".db"
+ print(string.format("Replacing '%s' with '%s'", destination, source));
+ assert(os.remove(destination))
+ helpers.copy_file(source, destination)
+ end
+end
+
+local function remove_cache()
+ if helpers.exists(paths.file_indexer_cache) then
+ print(string.format("Removing cache file '%s'", paths.file_indexer_cache))
+ assert(os.remove(paths.file_indexer_cache))
+ end
+end
+
+local function cleanup()
+ if helpers.exists(unpacked_backup_dir) then
+ helpers.rmdir(unpacked_backup_dir)
+ end
+end
+
+function restore.execute()
+ check_available_space()
+ unpack_backup()
+ perform_db_migration()
+ sync_databases()
+ remove_cache()
+ cleanup()
+end
+
+return restore
A scripts/lua/products/BellHybrid/scripts/update.lua => scripts/lua/products/BellHybrid/scripts/update.lua +145 -0
@@ 0,0 1,145 @@
+local update = {}
+local lfs = require('lfs')
+local json = require('lunajson')
+local paths = require('paths')
+local consts = require('consts')
+local helpers = require('helpers')
+local recovery = require('recovery')
+local migration = require('migration')
+
+update.script_name = "update.lua"
+update.img_in_progress = "assets/gui_image_update_in_progress.bin"
+update.img_success = "assets/gui_image_update_success.bin"
+update.img_failure = "assets/gui_image_update_failed.bin"
+update.img_in_progress_wait = false -- To display progress bar immediately after the script has started
+
+-- Match only files with '.db' extensions and omit such files inside subdirectories
+local match_db_files = '^[^%/]*%.db$'
+
+-- Percent values to be shown on progress bar taking each step's processing time into account
+local update_stages = {
+ init = 0,
+ stage1 = 4,
+ stage2 = 8,
+ stage3 = 72,
+ stage4 = 75,
+ stage5 = 77,
+ stage6 = 80,
+ stage7 = 82,
+ stage8 = 85,
+ done = 100
+}
+
+local function update_progress(percent)
+ local x, y = 90, 354
+ local height_empty, height_full = 4, 8
+ local width = 420
+ recovery.gui.draw_progress_bar(x, y, width, height_empty, height_full, percent)
+ recovery.gui.refresh_fast()
+end
+
+local function build_db_set(file)
+ local contents = helpers.read_whole_file(file)
+ local root = json.decode(contents)
+ local set = {}
+ for _, entry in pairs(root.databases) do
+ set[entry.name] = tonumber(entry.version)
+ end
+ return set
+end
+
+local function purge_target_slot()
+ local target_dir = recovery.sys.target_slot()
+ print(string.format("Removing target slot content, '%s'", target_dir))
+ helpers.rmdir_content(target_dir)
+end
+
+local function copy_update_package()
+ local target_dir = recovery.sys.target_slot()
+ print(string.format("Copying content of the update package '%s to '%s'", paths.update_dir, target_dir))
+ helpers.copy_dir(paths.update_dir, target_dir)
+end
+
+local function copy_databases()
+ local from = paths.db_dir
+ local to = recovery.sys.target_slot() .. "/db"
+ print(string.format("Copying databases from '%s' to '%s'", from, to))
+ helpers.copy_dir_filtered(from, to, match_db_files)
+end
+
+local function create_directories()
+ print("Creating 'log' and 'crash_dumps' directories")
+
+ local target_dir = recovery.sys.target_slot()
+ lfs.mkdir(target_dir .. "/log")
+ lfs.mkdir(target_dir .. "/crash_dumps")
+ lfs.mkdir(target_dir .. "/var")
+end
+
+local function migrate_db()
+ local version_file = paths.update_dir .. "/" .. consts.version_file
+
+ print("Performing database migration")
+ local dbset = build_db_set(version_file)
+ local result = migration.migrate(paths.target.db_dir, paths.target.migration_scripts_dir, dbset)
+ assert(result == migration.retcode.OK, string.format("Database migration process failed with %d", result))
+end
+
+local function remove_cache()
+ if helpers.exists(paths.target.file_indexer_cache) then
+ print(string.format("Removing cache file '%s'", paths.target.file_indexer_cache))
+ assert(os.remove(paths.target.file_indexer_cache))
+ end
+end
+
+local function copy_var_directory()
+ local from = paths.var_dir
+ local to = paths.target.var_dir
+ print(string.format("Copying '%s' to '%s'", from, to))
+ helpers.copy_dir(from, to)
+end
+
+local function enter()
+ -- Mark the current slot as successful
+ recovery.bootctrl.mark_as_successful()
+ -- Mark the target slot as unbootable
+ recovery.bootctrl.mark_as_unbootable(recovery.bootctrl.get_next_active())
+end
+
+local function exit()
+ print("Finishing update process...")
+
+ helpers.rmdir(paths.update_dir)
+ os.remove(paths.update_file)
+
+ -- Update the working directory to the newly updated scripts directory
+ lfs.chdir(recovery.sys.target_slot() .. "/scripts")
+
+ recovery.bootctrl.mark_as_bootable(recovery.bootctrl.get_next_active())
+ recovery.bootctrl.mark_as_active(recovery.bootctrl.get_next_active())
+ recovery.sys.set_os_boot_status(false)
+end
+
+function update.execute()
+ update_progress(update_stages.init)
+ enter()
+ update_progress(update_stages.stage1)
+ purge_target_slot()
+ update_progress(update_stages.stage2)
+ copy_update_package()
+ update_progress(update_stages.stage3)
+ copy_databases()
+ update_progress(update_stages.stage4)
+ create_directories()
+ update_progress(update_stages.stage5)
+ migrate_db()
+ update_progress(update_stages.stage6)
+ copy_var_directory()
+ update_progress(update_stages.stage7)
+ remove_cache()
+ update_progress(update_stages.stage8)
+ exit()
+ update_progress(update_stages.done)
+end
+
+return update
R scripts/lua/backup.lua => scripts/lua/products/PurePhone/scripts/backup.lua +0 -0
R scripts/lua/entry.lua => scripts/lua/products/PurePhone/scripts/entry.lua +0 -1
@@ 83,4 83,3 @@ print_boot_reason()
local boot_reason, boot_reason_str = handle_boot_reason()
local status, message = invoke_script(boot_reason)
handle_exit(boot_reason_str, status, message)
-
A scripts/lua/products/PurePhone/scripts/factory.lua => scripts/lua/products/PurePhone/scripts/factory.lua +34 -0
@@ 0,0 1,34 @@
+local helpers = require('helpers')
+local paths = require('paths')
+
+local factory = {}
+
+factory.script_name = "factory.lua"
+factory.img_in_progress = "assets/gui_image_factory_reset_in_progress.bin"
+factory.img_success = "assets/gui_image_factory_reset_success.bin"
+factory.img_failure = "assets/gui_image_factory_reset_failed.bin"
+
+local function remove_old_databases()
+ print(string.format("Removing old databases from '%s'", paths.db_dir))
+ helpers.rm_files_from_dir(paths.db_dir)
+end
+
+local function copy_clean_databases()
+ print(string.format("Copying clean databases from '%s' to '%s'", paths.db_factory_dir, paths.db_dir))
+ helpers.copy_dir(paths.db_factory_dir, paths.db_dir)
+end
+
+local function remove_cache()
+ if helpers.exists(paths.file_indexer_cache) then
+ print(string.format("Removing cache file '%s'", paths.file_indexer_cache))
+ assert(os.remove(paths.file_indexer_cache))
+ end
+end
+
+function factory.execute()
+ remove_old_databases()
+ copy_clean_databases()
+ remove_cache()
+end
+
+return factory
R scripts/lua/restore.lua => scripts/lua/products/PurePhone/scripts/restore.lua +0 -0
R scripts/lua/update.lua => scripts/lua/products/PurePhone/scripts/update.lua +1 -1
@@ 78,7 78,7 @@ local function copy_var_directory()
end
local function enter()
- -- Mark the current slot as successful
+ -- Mark the current slot as successful
recovery.bootctrl.mark_as_successful()
-- Mark the target slot as unbootable
recovery.bootctrl.mark_as_unbootable(recovery.bootctrl.get_next_active())
M scripts/lua/share/paths.lua => scripts/lua/share/paths.lua +1 -1
@@ 14,7 14,7 @@ paths.db_factory_dir = paths.db_dir .. "/factory"
paths.temp_dir = user_dir .. "/temp"
paths.update_dir = user_dir .. "/temp/update"
paths.migration_scripts_dir = paths.db_dir .. "/migration"
-paths.user_relaxation_file = user_dir .. "/media/app/relaxation"
+paths.user_relaxation_file = user_dir .. "/media/app/relaxation" -- Only for Harmony
local target = {}
target.var_dir = target_dir .. "/var"