olivergregorius / micropython_ota

Micropython library for upgrading code over-the-air (OTA)
MIT License
27 stars 8 forks source link

Connection problems while updating may corrupt software #17

Closed lkies closed 1 year ago

lkies commented 1 year ago

As the title says, since the files are replaced as they are downloaded, the software can get into an inconsistent state. I suggest downloading all the files to a temporary folder and then replacing them once all of them have been downloaded. This should significantly reduce the risk of corruption if the connection is not reliable enough.

Here is how I implemented it in my version:

# def ota_update(host, project, filenames, use_version_prefix=True, user=None, passwd=None, hard_reset_device=True, soft_reset_device=False, timeout=5) -> None:          # REMOVED
def ota_update(host, project, filenames, tempdir, *,use_version_prefix=True, user=None, passwd=None, hard_reset_device=True, soft_reset_device=False, timeout=5) -> None: # ADDED
    all_files_found = True
    auth = generate_auth(user, passwd)
    prefix_or_path_separator = '_' if use_version_prefix else '/'
    try:
        version_changed, remote_version = check_version(host, project, auth=auth, timeout=timeout)
        if version_changed:
            try: uos.mkdir(tempdir) # ADDED
            except: pass            # ADDED
            for filename in filenames:
                if auth:
                    response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', headers={'Authorization': f'Basic {auth}'}, timeout=timeout)
                else:
                    response = urequests.get(f'{host}/{project}/{remote_version}{prefix_or_path_separator}{filename}', timeout=timeout)
                response_status_code = response.status_code
                response_text = response.text
                response.close()
                if response_status_code != 200:
                    print(f'Remote source file {host}/{project}/{remote_version}{prefix_or_path_separator}{filename} not found')
                    all_files_found = False
                    continue
                # with open(filename, 'w') as source_file:              # REMOVED
                with open(f"{tempdir}/{filename}", 'w') as source_file: # ADDED
                    source_file.write(response_text)                    # ADDED
            if all_files_found:
                for filename in filenames:                                            # ADDED
                    temp_filename = f"{tempdir}/{filename}"                           # ADDED
                    with open(temp_filename,"r") as temp, open(filename,"w") as file: # ADDED
                        file.write(temp.read())                                       # ADDED
                    uos.remove(temp_filename)                                         # ADDED
                uos.rmdir(tempdir)                                                    # ADDED
                with open('version', 'w') as current_version_file:
                    current_version_file.write(remote_version)
                if soft_reset_device:
                    print('Soft-resetting device...')
                    machine.soft_reset()
                if hard_reset_device:
                    print('Hard-resetting device...')
                    machine.reset()
    except Exception as ex:
        print(f'Something went wrong: {ex}')
olivergregorius commented 1 year ago

Well that's a nice solution. Thank you very mucht @lkies for your contribution. I will include this in the next release.

olivergregorius commented 1 year ago

@lkies Your suggested changes have been released in version 2.1.0.