Changeset - 8551913bda80
[Not reviewed]
0 1 0
Branko Majic (branko) - 5 years ago 2020-07-05 15:50:03
branko@majic.rs
Noticket: Implement additional path validations for Factorio Manager:

- Added test for instance directories, new backup directories, backup
directories, and import sources.
- Refactored commands to use the new path validations.
- Updated some comments and formatting.
1 file changed with 156 insertions and 190 deletions:
0 comments (0 inline, 0 general)
games/factorio_manager.sh
Show inline comments
 
@@ -798,130 +798,193 @@ function read_server_settings() {
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        if [[ ${settings_type[$key]} == "str" ]]; then
 
            settings_value[$key]="\"${settings_value[$key]}\""
 

	
 
        # Used for selecting between multiple values.
 
        elif [[ ${settings_type[$key]} =~ ^.+\|.+$ ]]; then
 
            readarray -d "|" -t possible_values < <(echo -n "${settings_type[$key]}")
 

	
 
            for i in "${possible_values[@]}"; do
 
                # Convenience for allowing strings without quotes specified by the user.
 
                [[ \"${settings_value[$key]}\" == $i ]] && settings_value[$key]="\"${settings_value[$key]}\""
 
            done
 

	
 
        # List of strings.
 
        elif [[ ${settings_type[$key]} == "list" ]]; then
 
            value=""
 

	
 
            for item in ${settings_value[$key]}; do
 
                value="$value, \"$item\""
 
            done
 

	
 
            settings_value[$key]="[${value##, }]"
 
        fi
 
        debug "Processed value for $key: ${settings_value[$key]}"
 
    done
 
}
 

	
 
#
 
# Validates paths according to internal tool rules or terminates the
 
# program.
 
#
 
# This function helps to reduce boilerplate and centralise some common
 
# checks around handling of various paths.
 
#
 
# Arguments:
 
#
 
#   $1 (path_test)
 
#     Test to apply against the path. Supported tests are:
 
#
 
#       game_installations_directory
 
#         Checks if path contains Factorio installations in
 
#         sub-directories.
 
#
 
#       instance_directory_new
 
#         Checks if path is unused and can be used for new instance.
 
#
 
#       instance_directory
 
#         Checks if path contains a valid instance.
 
#
 
#       backup_directory_new
 
#         Checks if path is usable as destination for a new backup.
 
#
 
#       backup_directory
 
#         Checks if path contains a valid backup.
 
#
 
#       instance_import_source
 
#         Checks if path is usable as source for importing an instance.
 
#
 
#   $2 (path)
 
#     Path that should be validated.
 
#
 
#   $3 (exit_code0
 
#   $3 (exit_code)
 
#     Exit code to use when terminating the program.
 
#
 
# Exits:
 
#
 
#   If the path type is unsupported, or if the specified path does not
 
#   conform to requirements for the specified path type.
 
#
 
function validate_path_or_terminate() {
 
    local path_test="$1"
 
    local path="$2"
 
    local exit_code="$3"
 

	
 
    if [[ $path_test == "game_installations_directory" ]]; then
 
        # Make sure user has set directory with game installations -
 
        # test both symlink and target destination.
 
        if [[ ! -L $game_installations_directory || ! -d $game_installations_directory ]]; then
 
            error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
            exit "$exit_code"
 
        fi
 

	
 
    elif [[ $path_test == "instance_directory_new" ]]; then
 
        if [[ -e $path/config.ini ]]; then
 
            error "Instance already exists."
 
            exit "$exit_code"
 
        fi
 

	
 
        if [[ -e $path ]]; then
 
            error "Instance directory already exists, but does not contain a valid Factorio instance: $path."
 
            exit "$exit_code"
 
        fi
 

	
 
    elif [[ $path_test == "instance_directory" ]]; then
 
        if [[ ! -d $path ]]; then
 
            error "Missing instance directory: $path"
 
            error "Perhaps you have misstyped the instance name or forgot to create one first?"
 
            exit "$exit_code"
 
        fi
 

	
 
        if [[ ! -f $path/instance.conf ]]; then
 
            error "Missing instance configuration file: $path/instance.conf"
 
            exit "$exit_code"
 
        fi
 

	
 
        if [[ ! -f $path/config.ini ]]; then
 
            error "Missing game configuration file: $path/config.ini"
 
            exit "$exit_code"
 
        fi
 

	
 
    elif [[ $path_test == "backup_directory_new" ]]; then
 
        if [[ -e $path/config.ini ]]; then
 
            error "Backup already exists under directory: $path"
 
            exit "$exit_code"
 
        elif [[ -e $path ]]; then
 
            error "Backup directory already exists, but does not contain a valid backup: $path"
 
            exit "$exit_code"
 
        fi
 

	
 
    elif [[ $path_test == "backup_directory" ]]; then
 

	
 
        if [[ ! -d $path ]]; then
 
            error "Specified backup not available under: $path"
 
            exit "$ERROR_ARGUMENTS"
 
        fi
 

	
 
        if [[ ! -f $path/instance.conf ]]; then
 
            error "Backup missing instance configuration file: $path/instance.conf"
 
            exit "$exit_code"
 
        fi
 

	
 
        if [[ ! -f $path/config.ini ]]; then
 
            error "Backup missing game configuration file: $path/config.ini"
 
            exit "$exit_code"
 
        fi
 

	
 
    elif [[ $path_test == "instance_import_source" ]]; then
 
        if [[ ! -f $path/bin/x64/factorio ]]; then
 
            error "Could not locate Factorio binary in directory under: $path/bin/x64/factorio"
 
            error "Factorio Manager natively supports instance imports only when all game data (savegames etc) are stored within Factorio installation directory."
 
            exit "$ERROR_ARGUMENTS"
 
        fi
 

	
 
    else
 
        error "Unable to validate path '$path', unsupported test requested: '$path_test'."
 
        exit "$ERROR_GENERAL"
 
    fi
 
}
 

	
 
#
 
# Prompts user to pick version of Factorio.
 
#
 
# Arguments:
 
#
 
#   $1 (game_installations_directory)
 
#     Path to directory containing Factorio installations.
 
#
 
#   $2 (default_version, optional)
 
#     Default version to fill-in for the user. If not set, last
 
#     version in the directory (sorted alphabetically) will be used.
 
#
 
#   $3 (default_marker, optional)
 
#     Marker text to use for the default version. Default is "default".
 
#
 
# Returns:
 
#
 
#   0 if version was successfully selected, 1 otherwise.
 
#
 
# Sets:
 
#
 
#   game_version_selected
 
#
 
function select_factorio_version() {
 
    local game_installations_directory="$1"
 
    local default_version="${2-}"
 
    local default_marker="${3-default}"
 

	
 
    local game_versions_available=()
 
    local candidate
 
    local i default_option selected_option
 

	
 
    declare -g game_version_selected=""
 

	
 
    # Grab a list of available versions.
 
    for candidate in "$game_installations_directory"/*; do
 
        if [[ -f $candidate/bin/x64/factorio ]]; then
 
            game_versions_available+=( "$(basename "$candidate")" )
 
        fi
 
    done
 

	
 
    if [[ ${#game_versions_available[@]} == 0 ]]; then
 
@@ -965,1290 +1028,1193 @@ function select_factorio_version() {
 
}
 

	
 

	
 
# Define error codes.
 
SUCCESS=0
 
ERROR_ARGUMENTS=1
 
ERROR_CONFIGURATION=2
 
ERROR_GENERAL=3
 

	
 
# Disable debug and quiet modes by default.
 
debug=0
 
quiet=0
 

	
 
# Set-up some default paths.
 
manager_directory="$HOME/.factorio"
 
game_installations_directory="$manager_directory/.game_installations"
 

	
 
# If no arguments were given, just show usage help.
 
if [[ -z ${1-} ]]; then
 
    short_usage
 
    exit "$SUCCESS"
 
fi
 

	
 
# Parse the arguments
 
while getopts "qdvh" opt; do
 
    case "$opt" in
 
	q) quiet=1;;
 
	d) debug=1;;
 
        v) version
 
           exit "$SUCCESS";;
 
        h) usage
 
           exit "$SUCCESS";;
 
        *) usage
 
           exit "$ERROR_ARGUMENTS";;
 
    esac
 
done
 
i=$OPTIND
 
shift $(($i-1))
 

	
 
# Make sure the manager home directory exists.
 
if [[ ! -e $manager_directory ]]; then
 
    info "Creating Factorio Manager home directory under: $manager_directory"
 
    mkdir -p "$manager_directory"
 
fi
 

	
 
command="$1"
 
shift
 

	
 

	
 
#==============#
 
# set-game-dir #
 
#==============#
 
if [[ $command == set-game-dir ]]; then
 

	
 
    # Read and verify additional positional arguments.
 
    game_installations_directory_target="${1-}"
 
    shift
 

	
 
    if [[ -z $game_installations_directory_target ]]; then
 
        error "Missing argument: GAME_INSTALLATIONS_DIRECTORY"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -e $game_installations_directory_target ]]; then
 
        error "No such directory: $game_installations_directory"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    # Verify that at least one Factorio instance can be found.
 
    factorio_versions_found=0
 
    for candidate in "$game_installations_directory_target"/*; do
 
        if [[ -f $candidate/bin/x64/factorio ]]; then
 
            let factorio_versions_found++
 
        fi
 
    done
 

	
 
    if (( $factorio_versions_found == 0 )); then
 
        error "Could not locate any Factorio installations under: $game_installations_directory_target"
 

	
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    # Update the link
 
    if [[ -L $game_installations_directory ]]; then
 
        rm "$game_installations_directory"
 
    fi
 

	
 
    if ! ln -s "$game_installations_directory_target" "$game_installations_directory"; then
 
        error "Could not create symlink from '$game_installations_directory_target' to '$game_installations_directory'."
 

	
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 

	
 
#==========#
 
# versions #
 
#==========#
 
elif [[ $command == versions ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    echo "Locally available Factorio versions:"
 
    echo
 

	
 
    # Find all sub-directories that are Factorio installations.
 
    for candidate in "$game_installations_directory"/*; do
 
        if [[ -d $candidate && -e $candidate/bin/x64/factorio ]]; then
 
            echo "  - $(basename "$candidate")"
 
        fi
 
    done
 
    echo
 

	
 

	
 
#======#
 
# list #
 
#======#
 
elif [[ $command == list ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    echo "Available instances:"
 
    echo
 

	
 
    # Find all sub-directories that are valid instances.
 
    for candidate in "$manager_directory"/*; do
 
        if [[ -f $candidate/instance.conf ]]; then
 
            source "$candidate/instance.conf"
 
            echo "  - $(basename "$candidate") ($game_version)"
 
        fi
 
    done
 
    echo
 

	
 

	
 
#========#
 
# create #
 
#========#
 
elif [[ $command == create ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Calculate derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 

	
 
    # Verify arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Make sure new instance directory can be used.
 
    validate_path_or_terminate "instance_directory_new" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    select_factorio_version "$game_installations_directory" || exit "$ERROR_GENERAL"
 

	
 
    # Set-up the instance.
 
    mkdir "$instance_directory"
 
    mkdir "$instance_directory/mods"
 
    echo "{}" > "$instance_directory/mods/mod-list.json"
 

	
 
    cat <<EOF > "$instance_config"
 
game_version="$game_version_selected"
 
EOF
 

	
 
    # @TODO: If we could somehow obtain stock config.ini file without
 
    # having to first run Factorio, that would be great. As it is, the
 
    # user will presented with warning about corrupt config.ini when
 
    # running the instance for the first time.
 
    cat <<EOF > "$game_config"
 
[path]
 
read-data=__PATH__executable__/../../data
 
write-data=${instance_directory}
 

	
 
[general]
 
locale=
 

	
 
[other]
 
check-updates=false
 
enable-crash-log-uploading=false
 

	
 
[interface]
 

	
 
[controls]
 

	
 
[sound]
 

	
 
[map-view]
 

	
 
[debug]
 

	
 
[multiplayer-lobby]
 

	
 
[graphics]
 
EOF
 

	
 
    echo
 
    warning "Factorio Manager has created a minimal empty configuration file for Factorio under $game_config."
 
    warning "Since the generated configuration file is almost empty, Factorio will complain that the file seems corrupt."
 
    warning "Factorio will offer to fix the corrupted configuration file by filling-in the missing information during the first startup."
 
    warning "It should be safe to accept this. This warning will be shown by Factorio only the first time."
 

	
 

	
 
#========#
 
# launch #
 
#========#
 
elif [[ $command == launch ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Set-up derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 
    server_config="$instance_directory/server-settings.json"
 

	
 
    # Verify arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $game_config ]]; then
 
        error "Missing game configuration file: $game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Update instance write directory prior to launching - this is a
 
    # failsafe in case it got changed by hand through some other means
 
    # (or maybe it was copied over from another instance).
 
    # failsafe in case it got changed by hand or perhaps the value
 
    # comes from backups of a copied instance.
 
    current_write_data=$(grep "^write-data=" "$game_config")
 
    expected_write_data="write-data=${instance_directory}"
 

	
 
    if [[ $current_write_data != $expected_write_data ]]; then
 
        warning "Incorrect path specified for write-data in game configuration file: $game_config"
 
        warning "Current configuration is: $current_write_data"
 
        warning "Configuration will be replaced with: $expected_write_data"
 
        sed -i -e "s#^write-data=.*#$expected_write_data#" "$game_config"
 
    fi
 

	
 
    # Read launcher configuration for the instance.
 
    source "$instance_config"
 

	
 
    if [[ -z $game_version ]]; then
 
        error "Missing game version information in $game_config."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    # Set-up paths for launching the game, and ensure they still exist
 
    # (versions can be removed by user).
 
    game_directory="${game_installations_directory}/${game_version}"
 
    factorio_bin="$game_directory/bin/x64/factorio"
 

	
 
    if [[ ! -e $factorio_bin ]]; then
 
        error "Could not locate Factorio binary under: $factorio_bin"
 
        error "Factorio $game_version installation may have been removed from game installations directory:"
 
        error "   $(readlink -f "$game_installations_directory")"
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    # Launch instance
 
    if [[ -e $server_config ]]; then
 
        "$factorio_bin" --config "$game_config" --start-server "$instance_directory/saves/default.zip"
 
    else
 
        "$factorio_bin" --config "$game_config"
 
    fi
 

	
 

	
 
#========#
 
# backup #
 
#========#
 
elif [[ $command == backup ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    description="${2-}"
 
    shift 2
 

	
 
    # Use timestamp-based names for backups.
 
    timestamp=$(date +%Y-%m-%d_%H:%M:%S)
 

	
 
    # Set-up derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 
    lock_file="$instance_directory/.lock"
 

	
 
    backup_directory="$instance_directory/.bak"
 
    backup_destination="$backup_directory/$timestamp"
 
    backup_description="$backup_destination/.description"
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Verify that we are trying to backup an actual instance.
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $game_config ]]; then
 
        error "Missing game configuration file: $game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Make sure we do not overwrite the backup destination by mistake
 
    # - although highly unlikely unless user spams the backup command.
 
    if [[ -e $backup_destination ]]; then
 
        error "Backup already exists: $backup_destination"
 
    fi
 
    # Validate destination path for backup directory can be used.
 
    validate_path_or_terminate "backup_directory_new" "$backup_destination" "$ERROR_GENERAL"
 

	
 
    (
 

	
 
        # Obtain lock - Factorio uses the same mechanism, so we should
 
        # be able to detect the game is running in this way.
 
        flock --exclusive --nonblock 200
 
        if [[ $? != 0 ]]; then
 
            error "Could not lock instance directory via lock file $lock_file. Is Factorio instance still running?"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        function remove_lock() {
 
            rm "$lock_file"
 
        }
 

	
 
        trap remove_lock EXIT
 

	
 
        # Backup the instance. Clean-up the backup destination in case of failure.
 
        mkdir -p "$backup_destination"
 
        if ! cp -a "$instance_directory"/* "$backup_destination"; then
 
            error "Could not create backup under: $backup_destination"
 
            rm -rf "$backup_destination"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        # Store (optional) description.
 
        if [[ -n $description ]]; then
 
            echo "$description" > "$backup_description"
 
        fi
 

	
 
        success "Backup saved to: $backup_destination"
 

	
 
    ) 200>"$lock_file"
 

	
 

	
 
#==============#
 
# list-backups #
 
#==============#
 
elif [[ $command == list-backups ]]; then
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Set-up derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 
    backup_directory="$instance_directory/.bak"
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Verify instance.
 
    if [[ ! -d $instance_directory ]]; then
 
        error "No such instance: $instance"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $game_config ]]; then
 
        error "Missing game configuration file: $game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Extended regular expression for matching YYYY-MM-DD-hh:mm:ss format.
 
    backup_destination_name_pattern="[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2}_[[:digit:]]{2}:[[:digit:]]{2}:[[:digit:]]{2}"
 

	
 
    # Read current game version.
 
    source "$instance_config"
 
    current_game_version="$game_version"
 
    unset game_version
 

	
 
    # Detect and show available backups to the user.
 
    if [[ ! -d $backup_directory ]] || ! ls "$backup_directory" | grep -q -E "^$backup_destination_name_pattern\$"; then
 
        echo "No backups are available for instance $(colorecho -n green "$instance")."
 
    else
 
        echo "Available backups for instance $(colorecho -n green "$instance") (current version $(colorecho -n green "$current_game_version"))."
 
        echo
 

	
 
        for backup_destination in "$backup_directory"/*; do
 

	
 
            if [[ $backup_destination =~ $backup_destination_name_pattern ]]; then
 
                backup_date=$(basename "$backup_destination")
 

	
 
                # Read instance configuration for backup.
 
                source "$backup_destination/instance.conf"
 

	
 
                if [[ -f "$backup_destination/.description" ]]; then
 
                    backup_description=$(<"$backup_destination/.description")
 
                    echo "  - $backup_date - version $(colorecho -n green "$game_version") ($backup_description)"
 
                else
 
                    echo "  - $backup_date - version $(colorecho -n green "$game_version")"
 
                fi
 

	
 

	
 
            fi
 
        done
 

	
 
        echo
 

	
 
    fi
 

	
 

	
 
#=========#
 
# restore #
 
#=========#
 
elif [[ $command == restore ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    backup_name="${2-}"
 
    shift 2
 

	
 
    # Set-up derived values.
 
    instance_directory="$manager_directory/$instance"
 
    restore_source="$instance_directory/.bak/$backup_name"
 
    backup_instance_config="$restore_source/instance.conf"
 
    backup_game_config="$restore_source/config.ini"
 
    lock_file="$instance_directory/.lock"
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ -z $backup_name ]]; then
 
        error "Missing argument: BACKUP_NAME"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Verify we are working with legitimate instance and backup.
 
    if [[ ! -d $instance_directory ]]; then
 
        error "No such instance: $instance"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -d $restore_source ]]; then
 
        error "Specified backup not available under: $restore_source"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    if [[ ! -f $backup_instance_config ]]; then
 
        error "Invalid backup, missing instance configuration file: $backup_instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $backup_game_config ]]; then
 
        error "Invalid backup, missing game configuration file: $backup_game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that backup directory contains valid backup.
 
    validate_path_or_terminate "backup_directory" "$restore_source" "$ERROR_ARGUMENTS"
 

	
 
    (
 
        # Obtain lock - Factorio uses the same mechanism, so we should
 
        # be able to detect the game is running in this way.
 
        flock --exclusive --nonblock 200
 
        if [[ $? != 0 ]]; then
 
            error "Could not lock instance directory via lock file $lock_file. Is Factorio instance still running?"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        function remove_lock() {
 
            rm "$lock_file"
 
        }
 
        trap remove_lock EXIT
 

	
 
        # Set-up a list of files and directories that will get removed.
 
        shopt -s nullglob
 
        entries_to_remove=($instance_directory/*)
 
        shopt -u nullglob
 

	
 
        # Present user with extensive warning on consequences.
 
        echo
 
        warning "You are about to replace current instance's files, including (but not limited to):"
 
        warning
 
        warning "  - configuration files"
 
        warning "  - blueprints"
 
        warning "  - savegames"
 
        warning "  - achievements"
 
        warning "  - mods"
 
        warning
 
        warning "All instance files will be replaced by files from specified backup."
 

	
 
        # Display backup information.
 
        echo
 
        if [[ -f "$restore_source/.description" ]]; then
 
            backup_description=$(<"$restore_source/.description")
 
            backup_info="$backup_name ($backup_description)"
 
        else
 
            backup_info="$backup_name"
 
        fi
 
        echo "Restore instance $(colorecho -n green "$instance") from backup: $(colorecho -n green "$backup_info")"
 
        echo
 

	
 
        # Show user what files will be removed.
 
        if [[ ${#entries_to_remove[@]} == 0 ]]; then
 
            echo "Instance directory is currently empty. No files will be removed."
 
            echo
 
        else
 
            echo "Files and directories that will be removed:"
 
            echo
 
            for entry in "${entries_to_remove[@]}"; do
 
                echo "  - $entry"
 
            done
 
            echo
 
        fi
 

	
 
        # Request from user to confirm the operation.
 
        critical_confirmation "Are you sure you want to proceed?" \
 
                              "Aborted restore process, no changes have been made to instance files." \
 
                              "$ERROR_GENERAL"
 

	
 
        if [[ ${#entries_to_remove[@]} != 0 ]]; then
 
            if ! rm -rf "${entries_to_remove[@]}"; then
 
                error "Failed to remove existing instance files."
 
                exit "$ERROR_GENERAL"
 
            fi
 
        fi
 

	
 
        if ! cp -a "$restore_source"/* "$instance_directory"; then
 
            error "Failed to restore backup from: $restore_source"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        success "Instance restored from backup."
 

	
 
    ) 200>"$lock_file"
 

	
 

	
 
#===============#
 
# remove-backup #
 
#===============#
 
elif [[ $command == remove-backup ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    backup_name="${2-}"
 
    shift 2
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ -z $backup_name ]]; then
 
        error "Missing argument: BACKUP_NAME"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Set-up derived values.
 
    instance_directory="$manager_directory/$instance"
 
    removal_target="$instance_directory/.bak/$backup_name"
 
    backup_instance_config="$removal_target/instance.conf"
 
    backup_game_config="$removal_target/config.ini"
 
    lock_file="$instance_directory/.lock"
 

	
 
    # Verify we are working with legitimate instance and backup.
 
    if [[ ! -d $instance_directory ]]; then
 
        error "No such instance: $instance"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    if [[ ! -d $removal_target ]]; then
 
        error "Specified backup not available under: $removal_target"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -f $backup_instance_config ]]; then
 
        error "Invalid backup, missing instance configuration file: $backup_instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $backup_game_config ]]; then
 
        error "Invalid backup, missing game configuration file: $backup_game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that backup directory contains valid backup.
 
    validate_path_or_terminate "backup_directory" "$removal_target" "$ERROR_ARGUMENTS"
 

	
 
    # Present user with warning.
 
    echo
 
    if [[ -f "$removal_target/.description" ]]; then
 
        backup_description=$(<"$removal_target/.description")
 
        backup_info="$backup_name ($backup_description)"
 
    else
 
        backup_info="$backup_name"
 
    fi
 

	
 
    warning "You are about to remove an instance backup. All files belonging to specified backup will be removed."
 
    echo
 
    echo "Instance: $(colorecho -n green "$instance")"
 
    echo
 
    echo "Backup: $(colorecho -n green "$backup_info")"
 
    echo
 
    echo "Files and directories that will be removed:"
 
    echo
 
    echo " - $removal_target"
 
    echo
 

	
 
    # Request from user to confirm the operation.
 
    critical_confirmation "Are you sure you want to proceed?" \
 
                          "Aborted backup removal, no changes have been made to backup files." \
 
                          "$ERROR_GENERAL"
 

	
 
    if ! rm -rf "$removal_target"; then
 
        error "Failed to remove existing instance files."
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    success "Backup removed."
 

	
 

	
 
#=============#
 
# set-version #
 
#=============#
 
elif [[ $command == set-version ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Make sure user has set directory with game installations - test
 
    # both symlink and target destination.
 
    if [[ ! -L $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    if [[ ! -d $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    # Set-up derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 

	
 
    # Verify we are working with legitimate instance.
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $game_config ]]; then
 
        error "Missing game configuration file: $game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Load instance configuration.
 
    source "$instance_config"
 

	
 
    # Display list of available Factorio versions.
 
    echo "Current version used for instance $(colorecho -n green "$instance") is $(colorecho -n green "$game_version")."
 
    echo
 
    select_factorio_version "$game_installations_directory" "$game_version" "current" || exit "$ERROR_GENERAL"
 
    echo
 

	
 
    # Change instance game version.
 
    if [[ $game_version_selected == $game_version ]]; then
 
        info "Current version has been kept."
 
    else
 
        sed -i -e "s/^game_version=.*/game_version=$game_version_selected/" "$instance_config"
 
        success "Version changed to: $(colorecho -n green "$game_version_selected")"
 
    fi
 

	
 

	
 
#======#
 
# info #
 
#======#
 
elif [[ $command == info ]]; then
 
    # Make sure user has set directory with game installations - test
 
    # both symlink and target destination.
 
    if [[ ! -L $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    if [[ ! -d $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Set-up derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 

	
 
    # Verify we are working with legitimate instance.
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $game_config ]]; then
 
        error "Missing game configuration file: $game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Load instance configuration.
 
    source "$instance_config"
 

	
 
    # Basic information.
 
    echo "Instance name: $(colorecho -n green "$instance")"
 

	
 
    if [[ -e "$game_installations_directory/$game_version" ]]; then
 
        echo "Game version: $(colorecho -n green "$game_version")"
 
    else
 
        echo "Game version: $(colorecho -n red "$game_version [not available locally]")"
 
    fi
 

	
 
    echo "Instance path: $(colorecho -n green "$instance_directory")"
 
    echo
 

	
 
    # Mod information.
 
    shopt -s nullglob
 
    mod_files=($instance_directory/mods/*.zip)
 
    mod_list="$instance_directory/mods/mod-list.json"
 
    shopt -u nullglob
 

	
 
    if [[ ${#mod_files[@]} == 0 ]]; then
 
        echo "Available mods: none"
 

	
 
    # Plain listing.
 
    # Listing using just regular tools.
 
    elif ! hash jqs 2>/dev/null; then
 
        echo "Available mods:"
 
        echo
 

	
 
        # Determine if mod is enabled by default or not when added to
 
        # mods directory (and is not yet listed in the mod list file).
 
        if grep -i -q "^[[:blank:]]*enable-new-mods=false" "$game_config"; then
 
            enable_new_mods="false"
 
        else
 
            enable_new_mods="true"
 
        fi
 

	
 
        # Process every mod found.
 
        for mod_file in "${mod_files[@]}"; do
 

	
 
            if [[ -f $mod_file ]]; then
 
                # Extract basic information about mod from the filename.
 
                mod_basename=$(basename "$mod_file" .zip)
 
                mod_name="${mod_basename%_*}"
 
                mod_version="${mod_basename##*_}"
 

	
 
                # Determine if mod is enabled or not.
 

	
 
                if grep -A1 "\"name\": \"$mod_name\"" "$mod_list" | tail -n1 | grep -q '"enabled": false'; then
 
                    color="$_text_yellow"
 
                elif grep -A1 "\"name\": \"$mod_name\"" "$mod_list" | tail -n1 | grep -q '"enabled": true'; then
 
                    color=""
 
                elif [[ $enable_new_mods == "false" ]]; then
 
                    color="$_text_yellow"
 
                else
 
                    color=""
 
                fi
 

	
 
                # Show colored-information for the mod.
 
                printf "$color  - %-48s %8s $_text_reset\n" "$mod_name" "$mod_version"
 
            fi
 

	
 
        done
 

	
 
    # Fancy listing with detection for enabled mods using jq.
 
    # Listing using the jq utility.
 
    elif hash jq 2>/dev/null; then
 
        echo "Available mods (enabled/${_text_yellow}disabled${_text_reset}):"
 
        echo
 

	
 
        # Determine if mod is enabled by default or not when added to
 
        # mods directory (and is not yet listed in the mod list file).
 
        if grep -i -q "^[[:blank:]]*enable-new-mods=false" "$game_config"; then
 
            enable_new_mods="false"
 
        else
 
            enable_new_mods="true"
 
        fi
 

	
 
        # Query string used in jq tool to determine if mod is enabled
 
        # or not. Take note that this string should remain
 
        # single-quoted, and that $ expansions are actually done
 
        # internally in jq itself. Query accepts two vars - mod_name
 
        # and enable_new_mods.
 
        jq_is_enabled_query='.mods | map(select(.name==$mod_name))[0] // {"name": "default", "enabled": $enable_new_mods} | .enabled'
 

	
 
        # Process every mod found.
 
        for mod_file in "${mod_files[@]}"; do
 

	
 
            if [[ -f $mod_file ]]; then
 

	
 
                # Extract basic information about mod from the filename.
 
                mod_basename=$(basename "$mod_file" .zip)
 
                mod_name="${mod_basename%_*}"
 
                mod_version="${mod_basename##*_}"
 

	
 
                # Determine if mod is enabled or not.
 
                if jq -e \
 
                      --arg "mod_name" "$mod_name" \
 
                      --argjson "enable_new_mods" "$enable_new_mods" \
 
                      "$jq_is_enabled_query" "$mod_list" > /dev/null; then
 
                    color=""
 
                else
 
                    color="$_text_yellow"
 
                fi
 

	
 
                # Show colored-information for the mod.
 
                printf "$color  - %-48s %8s $_text_reset\n" "$mod_name" "$mod_version"
 
            fi
 
        done
 
    fi
 
    echo
 

	
 
    # Call self for displaying list of backups. Better than duplicating code.
 
    "$program" list-backups "$instance" | sed -e "s/^Available backups for instance.*/Available backups:/"
 

	
 

	
 
#========#
 
# remove #
 
#========#
 
elif [[ $command == remove ]]; then
 

	
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Set-up derived values.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    lock_file="$instance_directory/.lock"
 

	
 
    # Verify we are working with legitimate instance and backup.
 
    if [[ ! -d $instance_directory ]]; then
 
        error "No such instance: $instance"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -f $instance_config ]]; then
 
        error "Missing instance configuration file: $instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    (
 
        # Obtain lock - Factorio uses the same mechanism, so we should
 
        # be able to detect the game is running in this way.
 
        flock --exclusive --nonblock 200
 
        if [[ $? != 0 ]]; then
 
            error "Could not lock instance directory via lock file $lock_file. Is Factorio instance still running?"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        # Removing lock file will most likely fail if we manage to
 
        # remove the instance.
 
        function remove_lock() {
 
            rm -f "$lock_file"
 
        }
 
        trap remove_lock EXIT
 

	
 
        # Set-up a list of files and directories that will get removed.
 
        shopt -s nullglob dotglob
 
        entries_to_remove=($instance_directory/*)
 
        shopt -u nullglob dotglob
 

	
 
        # Present user with extensive warning on consequences.
 
        echo
 
        warning "You are about to remove all instance's files, including (but not limited to):"
 
        warning
 
        warning "  - configuration files"
 
        warning "  - blueprints"
 
        warning "  - savegames"
 
        warning "  - achievements"
 
        warning "  - mods"
 
        warning "  - backups"
 
        warning
 
        warning "All instance files will be removed."
 
        echo
 

	
 
        # Show user what files will be removed.
 
        if [[ ${#entries_to_remove[@]} == 0 ]]; then
 
            echo "Instance directory is currently empty. No files will be removed."
 
            echo
 
        else
 
            echo "Files and directories that will be removed:"
 
            echo
 
            echo "  - $instance_directory"
 
            for entry in "${entries_to_remove[@]}"; do
 
                echo "  - $entry"
 
            done
 
            echo
 
        fi
 

	
 
        # Display instance information.
 
        source "$instance_config"
 
        echo "Instance name:    $(colorecho -n green "$instance")"
 
        echo "Instance version: $(colorecho -n green "$game_version")"
 
        echo
 

	
 
        # Request from user to confirm the operation.
 
        critical_confirmation "Are you sure you want to proceed?" \
 
                              "Aborted instance removal, no changes have been made to instance files." \
 
                              "$ERROR_GENERAL"
 

	
 
        if [[ ${#entries_to_remove[@]} != 0 ]]; then
 
            if ! rm -rf "${entries_to_remove[@]}"; then
 
                error "Failed to remove instance files."
 
                exit "$ERROR_GENERAL"
 
            fi
 
        fi
 

	
 
        if ! rmdir "$instance_directory"; then
 
            error "Failed to remove instance files."
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        success "Instance removed."
 

	
 
    ) 200>"$lock_file"
 

	
 

	
 
#======#
 
# copy #
 
#======#
 
elif [[ $command == copy ]]; then
 

	
 
    # Make sure user has set directory with game installations - test
 
    # both symlink and target destination.
 
    if [[ ! -L $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    if [[ ! -d $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    source_instance="${1-}"
 
    destination_instance="${2-}"
 
    shift 2
 

	
 
    # Verify positional arguments.
 
    if [[ -z $source_instance ]]; then
 
        error "Missing argument: SOURCE_INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ -z $destination_instance ]]; then
 
        error "Missing argument: DESTINATION_INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Calculate derived variables.
 
    source_instance_directory="$manager_directory/$source_instance"
 
    source_instance_config="$source_instance_directory/instance.conf"
 
    source_game_config="$source_instance_directory/config.ini"
 
    source_lock_file="$source_instance_directory/.lock"
 

	
 
    destination_instance_directory="$manager_directory/$destination_instance"
 
    destination_instance_config="$destination_instance_directory/instance.conf"
 

	
 
    # Verify we are working with legitimate source and destination.
 
    if [[ ! -d $source_instance_directory ]]; then
 
        error "No such instance: $source_instance"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ ! -f $source_instance_config ]]; then
 
        error "Missing instance configuration file: $source_instance_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 

	
 
    if [[ ! -f $source_game_config ]]; then
 
        error "Missing game configuration file: $source_game_config"
 
        exit "$ERROR_GENERAL"
 
    fi
 
    # Validate that source instance directory contains valid instance.
 
    validate_path_or_terminate "instance_directory" "$source_instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Make sure destination instance directory can be used.
 
    validate_path_or_terminate "instance_directory_new" "$destination_instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    (
 
        # Obtain lock - Factorio uses the same mechanism, so we should
 
        # be able to detect the game is running in this way.
 
        flock --exclusive --nonblock 200
 
        if [[ $? != 0 ]]; then
 
            error "Could not lock instance directory via lock file $source_lock_file. Is Factorio instance still running?"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        function remove_lock() {
 
            rm "$source_lock_file"
 
        }
 

	
 
        trap remove_lock EXIT
 

	
 
        # Load source instance configuration.
 
        source "$source_instance_config"
 

	
 
        # Display list of available Factorio versions.
 
        echo
 
        echo "If you wish to, you can now change version of Factorio used for destination instance, or keep the same version as for source instance."
 
        echo
 

	
 
        select_factorio_version "$game_installations_directory" "$game_version" "current" | exit "$ERROR_GENERAL"
 
        echo
 

	
 
        # Check if user wants to copy backup files as well.
 
        copy_backups=""
 

	
 
        until [[ $copy_backups == "y" || $copy_backups == "n" ]]; do
 
            echo
 
            read -n1 -p "Would you like to copy backup files as well? (y/n)" copy_backups
 
            echo
 
            copy_backups="${copy_backups,,}"
 

	
 
            if [[ $copy_backups != "y" && $copy_backups != "n" ]]; then
 
                echo
 
                error "Please answer only with 'y' or 'n'."
 
            fi
 
        done
 

	
 
        # Set-up a list of files and directories to copy.
 
        entries_to_copy=($source_instance_directory/*)
 

	
 
        if [[ $copy_backups == "y" && -e "$source_instance_directory/.bak" ]]; then
 
             entries_to_copy+=("$source_instance_directory/.bak")
 
        fi
 

	
 
        # Create copy of source instance.
 
        mkdir "$destination_instance_directory"
 
        cp -a "${entries_to_copy[@]}" "$destination_instance_directory/"
 

	
 
        # Update write-data directory of destination instance,
 
        # including the backups.
 
        write_data="write-data=${destination_instance_directory}"
 
        find "$destination_instance_directory/" -type f -name config.ini -exec \
 
             sed -i -e "s#^write-data=.*#$write_data#" '{}' \;
 

	
 
        sed -i -e "s/^game_version=.*/game_version=$game_version_selected/" "$destination_instance_config"
 

	
 
        success "Created new instance $(colorecho -n green "$destination_instance") using version $(colorecho -n green "$game_version_selected") as copy of instance $(colorecho -n green "$source_instance")."
 

	
 
    ) 200>"$source_lock_file"
 

	
 

	
 
#========#
 
# import #
 
#========#
 
elif [[ $command == import ]]; then
 

	
 
    # Make sure user has set directory with game installations - test
 
    # both symlink and target destination.
 
    if [[ ! -L $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 

	
 
    if [[ ! -d $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    source_directory="${2-}"
 
    shift 2
 

	
 
    # Verify positional arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    if [[ -z $source_directory ]]; then
 
        error "Missing argument: SOURCE_DIRECTORY"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Calculate derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 

	
 
    source_config="$source_directory/config/config.ini"
 
    source_lock_file="$source_directory/.lock"
 

	
 
    # Make sure new instance directory can be used.
 
    validate_path_or_terminate "instance_directory_new" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    # Make sure that the import source is valid (should be Factorio
 
    # installation directory).
 
    validate_path_or_terminate "instance_import_source" "$source_directory" "$ERROR_ARGUMENTS"
 

	
 
    # List of entries to import from the source directory.
 
    declare -A import_entries=()
 
    import_entries["achievements-modded.dat"]="contains achivement information for modded plays"
 
    import_entries["achievements.dat"]="contains achievement information for vanilla plays"
 
    import_entries["archive"]="contains desync reports"
 
    import_entries["blueprint-storage.dat"]="contains global (non-savegame specific) blueprints"
 
    import_entries["config/config.ini"]="contains game configuration, including things like shortcuts etc."
 
    import_entries["crop-cache.dat"]="purpose is not known"
 
    import_entries["factorio-current.log"]="contains logs from the currently running game"
 
    import_entries["factorio-previous.log"]="contains logs from the previously running game"
 
    import_entries["mods"]="contains mods and mod settings"
 
    import_entries["player-data.json"]="contains global information about the player, such as username, login token, chat history, etc."
 
    import_entries["saves"]="contains savegames"
 

	
 
    validate_path_or_terminate "instance_directory_new" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    if [[ ! -f $source_directory/bin/x64/factorio ]]; then
 
        error "Could not locate Factorio binary in source directory under: $source_directory/bin/x64/factorio"
 
        error "Factorio Manager natively supports instance imports only when all game data (savegames etc) are stored within Factorio installation directory."
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Display list of available Factorio versions and let user pick one.
 
    echo "Factorio version must  be selected manually for imported instances."
 
    echo
 
    select_factorio_version "$game_installations_directory" || exit "$ERROR_GENERAL"
 

	
 
    (
 

	
 
        # Obtain lock - Factorio uses the same mechanism, so we should
 
        # be able to detect the game is running in this way.
 
        flock --exclusive --nonblock 200
 
        if [[ $? != 0 ]]; then
 
            error "Could not lock instance directory via lock file $source_lock_file. Is Factorio instance still running?"
 
            exit "$ERROR_GENERAL"
 
        fi
 

	
 
        function remove_lock() {
 
            rm "$source_lock_file"
 
        }
 

	
 
        trap remove_lock EXIT
 

	
 
        # Create instance directory.
 
        mkdir "$instance_directory"
 

	
 
        # Sort the associative array keys.
 
        IFS=$'\n' import_entries_keys=($(sort <<<"${!import_entries[*]}"))
 
        unset IFS
 

	
 
        # Copy files and directories.
 
        echo
 
        declare -a missing_import_entries=()
 
        for source_entry in "${import_entries_keys[@]}"; do
 
            source_entry_path="$source_directory/$source_entry"
 
            if [[ -e $source_entry_path ]]; then
 
                info "Importing $(colorecho -n blue "$source_entry")..."
 

	
 
                if ! cp -a "$source_entry_path" "$instance_directory/"; then
 
                    error "Could not import $source_entry from $source_entry_path (see above for errors)."
 
                    exit "$ERROR_GENERAL"
 
                fi
 
            else
 
                missing_import_entries+=("$source_entry")
 
            fi
 

	
 
            # Copy the configuration file.
 
        done
 

	
 
        echo
 

	
 
        cat <<EOF > "$instance_config"
 
game_version="$game_version_selected"
 
EOF
 

	
 
        # Fix write-data in config.ini
 
        write_data="write-data=${instance_directory}"
 
        sed -i -e "s#^write-data=.*#$write_data#" "$game_config"
 

	
 
        if [[ ${#missing_import_entries[@]} != 0 ]]; then
 
            warning "A number of files or directories were missing from the specified source."
 
            warning "For some of the entries this is perfectly normal, but you should verify that no critical files have been missed by mistake before switching to using this instance."
 
            echo
 
            for missing_import_entry in "${missing_import_entries[@]}"; do
 
                echo "$(colorecho blue "$missing_import_entry"), ${import_entries[$missing_import_entry]}"
 
                echo
 
            done
 
            warning "Press any key to continue."
 
            read -n1
 
        fi
 

	
 
        success "Finished import of instance $(colorecho -n green "$instance")."
 

	
 
    ) 200>"$source_lock_file"
 

	
 

	
 
#===============#
 
# create-server #
 
#===============#
 
elif [[ $command == create-server ]]; then
 
    instance="${1-}"
 

	
 
    # Make sure user has set directory with game installations - test
 
    # both symlink and target destination.
 
    if [[ ! -L $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 
    # Make sure game installations directory has been set.
 
    validate_path_or_terminate "game_installations_directory" "$game_installations_directory" "$ERROR_CONFIGURATION"
 

	
 
    if [[ ! -d $game_installations_directory ]]; then
 
        error "Game installations directory has not been properly set. Please run the set-game-dir command first."
 
        exit "$ERROR_CONFIGURATION"
 
    fi
 
    instance="${1-}"
 

	
 
    # Read positional arguments.
 
    instance="${1-}"
 
    shift
 

	
 
    # Calculate derived variables.
 
    instance_directory="$manager_directory/$instance"
 
    instance_config="$instance_directory/instance.conf"
 
    game_config="$instance_directory/config.ini"
 
    server_config="$instance_directory/server-settings.json"
 
    saves_directory="$instance_directory/saves"
 
    main_save="$saves_directory/default.zip"
 

	
 
    # Verify arguments.
 
    if [[ -z $instance ]]; then
 
        error "Missing argument: INSTANCE"
 
        exit "$ERROR_ARGUMENTS"
 
    fi
 

	
 
    # Make sure new instance directory can be used.
 
    validate_path_or_terminate "instance_directory_new" "$instance_directory" "$ERROR_ARGUMENTS"
 

	
 
    select_factorio_version "$game_installations_directory" || exit "$ERROR_GENERAL"
 

	
 
    # Grab server settings from user.
 
    echo "You will now be prompted to provide settings for the server, with some pre-filled settings."
 
    echo "Do not change settings marked with [EXPERT] unless you know what you are doing."
 
    echo
 
    read_server_settings "$instance"
 

	
 
    # Set-up the instance.
 
    mkdir "$instance_directory"
 
    mkdir "$instance_directory/mods"
 
    echo "{}" > "$instance_directory/mods/mod-list.json"
 

	
 
    cat <<EOF > "$instance_config"
 
game_version="$game_version_selected"
 
EOF
 
    cat <<EOF > "$game_config"
 
[path]
 
read-data=__PATH__executable__/../../data
 
write-data=${instance_directory}
 

	
 
[general]
 
locale=
 

	
 
[other]
 
check-updates=false
 
enable-crash-log-uploading=false
 
port=${settings_value[port]}
 

	
 

	
 
[interface]
 

	
 
[controls]
 

	
 
[sound]
 

	
 
[map-view]
 

	
 
[debug]
 

	
 
[multiplayer-lobby]
 

	
 
[graphics]
 
EOF
 

	
 
    echo
0 comments (0 inline, 0 general)