feat(instance): support listing world zips with extract - #1894
Conversation
Reviewer's GuideAdds support for listing and extracting world zip files in instances, fixes handling of nested world directories and Chinese filenames during extraction, and updates toast and world models/UI to accommodate these behaviors. Sequence diagram for extracting a world zip into instance savessequenceDiagram
actor User
participant InstanceWorldsPage
participant InstanceContext
participant InstanceService
participant InstanceCommands
participant Fs
User->>InstanceWorldsPage: click world extract (ZIP)
InstanceWorldsPage->>InstanceContext: handleImportResources({ paths: [save.dirPath], decompress: true })
InstanceContext->>InstanceContext: toast({ title: General.extracting, status: loading })
InstanceContext->>InstanceService: copyResourcesToInstances(selectedPaths, [instanceId], InstanceSubdirType.Saves, true)
InstanceService->>InstanceCommands: copyResourcesToInstances
loop for each resource
InstanceCommands->>Fs: generate_unique_filename(tgt_path, base_name, false)
InstanceCommands->>Fs: ZipArchive::new + archive.extract(dest_path)
alt dest_path has single inner dir
InstanceCommands->>Fs: read_dir(dest_path)
InstanceCommands->>InstanceCommands: decode_zip_name(first_component)
InstanceCommands->>Fs: generate_unique_filename(tgt_path, .sjmcl_extract_decoded_name, false)
InstanceCommands->>Fs: rename(inner_dir, tmp_path)
InstanceCommands->>Fs: remove_dir_all(dest_path)
InstanceCommands->>Fs: generate_unique_filename(tgt_path, decoded_name, false)
InstanceCommands->>Fs: rename(tmp_path, final_path)
end
end
InstanceCommands-->>InstanceService: SJMCLResult<CopyResourcesResponse>
InstanceService-->>InstanceContext: response
InstanceContext->>InstanceContext: toast.close(toastId)
InstanceContext->>InstanceContext: toast({ title: response.message, status: success })
InstanceContext->>InstanceWorldsPage: onSuccessCallback() -> getWorldListWrapper(true)
Sequence diagram for retrieving world list including zip savessequenceDiagram
actor User
participant InstanceWorldsPage
participant InstanceService
participant InstanceCommands
participant WorldHelpers
participant Fs
User->>InstanceWorldsPage: open instance worlds page
InstanceWorldsPage->>InstanceService: retrieveWorldList(instanceId)
InstanceService->>InstanceCommands: retrieve_world_list
InstanceCommands->>Fs: get_subdirectories(worlds_dir)
loop for each world folder
InstanceCommands->>WorldHelpers: load_world_info_from_dir(path, has_difficulty_support)
WorldHelpers->>Fs: load_level_data_from_nbt
WorldHelpers-->>InstanceCommands: WorldInfo { is_zip: false, icon_src: dir path string }
end
InstanceCommands->>Fs: get_files_with_regex(worlds_dir, zip_pattern)
loop for each world zip
InstanceCommands->>WorldHelpers: load_world_info_from_zip(path, has_difficulty_support)
WorldHelpers->>Fs: File::open + ZipArchive::new
WorldHelpers->>Fs: read level.dat + icon.png
WorldHelpers->>WorldHelpers: deserialize::<Level>
WorldHelpers-->>InstanceCommands: WorldInfo { is_zip: true, icon_src: base64 image string }
end
InstanceCommands-->>InstanceService: Vec<WorldInfo>
InstanceService-->>InstanceWorldsPage: worldList
InstanceWorldsPage->>InstanceWorldsPage: render OptionItem
InstanceWorldsPage->>InstanceWorldsPage: show Tag ZIP when world.isZip
InstanceWorldsPage->>InstanceWorldsPage: use base64ImgSrc(world.iconSrc) for ZIP, convertFileSrc for folders
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="src-tauri/src/utils/fs.rs" line_range="53-62" />
<code_context>
/// ```
-pub fn generate_unique_filename(base_path: &Path, filename: &OsStr) -> PathBuf {
- let (name, extension) = split_filename(filename);
+pub fn generate_unique_filename(
+ base_path: &Path,
+ filename: &OsStr,
+ keep_extension: bool,
+) -> PathBuf {
+ let (name, extension) = if keep_extension {
+ split_filename(filename)
+ } else {
+ (filename.to_string_lossy().into_owned(), String::new())
+ };
let mut dest_path = base_path.join(filename);
let mut counter = 1;
</code_context>
<issue_to_address>
**issue (bug_risk):** The `keep_extension` flag isn't fully honored for the initial destination path.
When `keep_extension` is `false`, `dest_path` is still built from the original `filename` (including extension), and only numbered variants omit the extension. This makes the flag’s behavior inconsistent and may let an unwanted extension (e.g. `.zip`) slip through on the first path. Please build the initial `dest_path` from `name`/`extension` so `keep_extension` consistently applies to both the initial and numbered paths.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| pub fn generate_unique_filename( | ||
| base_path: &Path, | ||
| filename: &OsStr, | ||
| keep_extension: bool, | ||
| ) -> PathBuf { | ||
| let (name, extension) = if keep_extension { | ||
| split_filename(filename) | ||
| } else { | ||
| (filename.to_string_lossy().into_owned(), String::new()) | ||
| }; |
There was a problem hiding this comment.
issue (bug_risk): The keep_extension flag isn't fully honored for the initial destination path.
When keep_extension is false, dest_path is still built from the original filename (including extension), and only numbered variants omit the extension. This makes the flag’s behavior inconsistent and may let an unwanted extension (e.g. .zip) slip through on the first path. Please build the initial dest_path from name/extension so keep_extension consistently applies to both the initial and numbered paths.
Checklist
This PR is a ..
Related Issues
Description
Additional Context
Summary by Sourcery
Add support for listing and extracting zipped Minecraft worlds within instances, including proper handling of nested folders and encoded names.
New Features:
Bug Fixes:
Enhancements:
Build: