brendan-duncan / archive

Dart library to encode and decode various archive and compression formats, such as Zip, Tar, GZip, ZLib, and BZip2.
MIT License
401 stars 140 forks source link

Missing Documentation on How to Add Files to an Archive #54

Closed the0neWhoKnocks closed 6 years ago

the0neWhoKnocks commented 6 years ago

Perhaps it's not possible with this package, but I can't figure out how to create a new Archive from files within my app. For example I want to zip up some files within a sub-directory of my app. All the examples only call out manipulating an already existing archive file.

brendan-duncan commented 6 years ago

You're right, that documentation is missing. I'll try and get an example put together soon. The crux of it would be to make an Archive object and add ArchiveFile's to it, then encode the archive with the ZipEncoder.

the0neWhoKnocks commented 6 years ago

:laughing: yeah, I figured that much out. What I couldn't determine was the format in which to do that. Not sure how much effort it'd take for you to put an example together, but it'd be appreciated.

brendan-duncan commented 6 years ago

As I started looking into it, I realized the ZipEncoder could be made much better to support this type of thing. I had done it for creating tar files for directories (TarFileEncoder), since Dart pub was using tars, but hadn't done it for zip files. I should be done in a day or two.

brendan-duncan commented 6 years ago

I pushed a new version with the changes I added to make this easier. You can now zip a directory using:

import 'dart:io';
import 'package:archive/archive_io.dart';
main() {
  new ZipFileEncoder().zipDirectory(new Directory('foo'), filename: 'foo.zip');
}

You can also create an Archive object for a disk directory using the new function: Archive archive = createArchiveFromDirectory(new Directory('foo'));

the0neWhoKnocks commented 6 years ago

Sweet! My use case needs to archive a mixture of files and folders, how would I go about that? Also, the first example you gave, is that synchronous or does it return a future? Would need the ability to wait until the archives been created.

brendan-duncan commented 6 years ago

It's synchronous.

ZipFileEncoder {
    void zipDirectory(Directory dir, [String zip_filename]); // convenience function for doing the wrapping the following methods.
    void open(String zip_path);
    void addDirectory(Directory dir);
    void addFile(File file, [String filename]); // filename is the stored in the zip, so it can be a path relative to the zip root.
    void close();
}
var encoder = new ZipFileEncoder();
encoder.open('foo.zip');
encoder.addDirectory(new Directory('some_directory'));
encoder.addFile(new File('some_file'));
encoder.close();
brendan-duncan commented 6 years ago

Nothing in the library is async. If anyone wants async, they can put it in their own async wrapper. I don't do that much Dart programming these days, other than minimally maintaining a couple libraries I had written like this one, so I don't have much time to add any extensive changes like that to the library...my kids make sure I don't have that time :-)

the0neWhoKnocks commented 6 years ago

I'll give this a try later.

Looks like example.dart is the same in the new release. I imagine it'd be useful to others if that was updated with the examples you provided here - especially since it's kinda the point of this ticket :wink: .

Also, I'm assuming that encoder.open('foo.zip'); will create the file if it doesn't already exist. If it does, it could be more clear to the user if a pass through method of create was added (which would then just call open internally. So the above would then read - create, addDirectory, addFile, & close.

Anyway, thanks for the quick turn around.

the0neWhoKnocks commented 6 years ago

So things are kind of working, in the sense that files are being added to a zip archive. I'm passing in absolute paths to files and directories formatted like so

/data/user/0/APP_NAME/app_flutter/file1.json
/data/user/0/APP_NAME/app_flutter/file2.json
/data/user/0/APP_NAME/app_flutter/someFolder

In the above imagine the contents of someFolder are sub1.json, sub2.json.

The zip file contains this structure after I've called addFile & addDirectory on the above list.

/[folder_with_no_name]
  /data
    /user
      /0
        /APP_NAME
          /app_flutter
            - file1.json
            - file2.json
- sub1.json
- sub2.json

The contents of the folder are being added individually instead of in the folder they were originally in, and the files are added in a nested folder structure matching their absolute path.

the0neWhoKnocks commented 6 years ago

Ok, this is what I ended up with. Notice I had to inline the guts of addDirectory since the relative call wasn't referencing a parent directory (causing the contents to be outside of their original folder), the parent directory is workingDir in this case.

String workingDir = '/data/user/0/APP_NAME/app_flutter';
String archivePath = 'archiveName.zip';
List<String> fileNames = [
  'file1.json',
  'file2.json',
  'someFolder',
];

// create the file so the encoder has something to open
new File(archivePath);
// spin up a new encoder instance
ZipFileEncoder encoder = new ZipFileEncoder();

// iterate files and add them to the archive
encoder.open(archivePath);
fileNames.forEach((String currFile) {
  String absPath = '$workingDir/$currFile';

  if(FileSystemEntity.isFileSync(absPath)) {
    encoder.addFile(
      new File(absPath),
      path.relative(absPath, from: workingDir)
    );
  }
  else if(FileSystemEntity.isDirectorySync(absPath)){
    Directory dir = new Directory(absPath);
    List files = dir.listSync(recursive: true);

    for(File file in files){
      if(file is! File) continue;
      encoder.addFile(
        file,
        path.relative(file.path, from: workingDir)
      );
    }
  }
});
encoder.close();
brendan-duncan commented 6 years ago

Ah, you’re right, my bad. Guess I was trying to rush through it to quickly. I’ll add your fixes to make sure relative paths are stored when I get the chance later...and update the doc examples. I still haven’t figured out how pub expects to use that example.dart file. Doesn’t make much sense to me for how to put multiple examples there. The wiki makes more sense to me.

brendan-duncan commented 6 years ago

I made a couple changes, hopefully improve things.

So, in your above example,

var encoder = new ZipFileEncoder();
encoder.create('archiveName.zip');
encoder.addFile(new File('/data/user/0/APP_NAME/app_flutter/file1.json'));
encoder.addFile(new File(/data/user/0/APP_NAME/app_flutter/file2.json'));
encoder.addDirectory(new Directory('/data/user/0/APP_NAME/app_flutter/someFolder'));
encoder.close();

should create a zip with:

file1.json
file2.json
someFolder/sub1.json
someFolder/sub2.json

I'm out of time for tonight, but I'll try and get the docs/examples updated soon and push out the release with the updates.

the0neWhoKnocks commented 6 years ago

With the includeDirName arg, would it maintain the full path from a specified directory? For example, if someFolder had a file that was nested a couple folders deep, would it maintain the full directory structure, or would it flatten everything to be in just one folder?

Maybe just passing a workingDir arg on creation of the encoder might be preferable. Then each addFile or addDirectory call would know what to base it's relative pathing on.

var encoder = new ZipFileEncoder({
  workingDir: '/data/user/0/APP_NAME/app_flutter', 
});
brendan-duncan commented 6 years ago

Relative paths are stored in both cases of includeDirName, it's just weather or not the directory name itself is included. If you have the following directory/files:

/data/user/0/APP_NAME/app_flutter/someFolder/subFolder/file1.json
/data/user/0/APP_NAME/app_flutter/someFolder/file2.json

Then: addDirectory('/data/user/0/APP_NAME/app_flutter/someFolder', includeDirName: false) Would store the files as:

subFolder/file1.json
file2.json

And addDirectory('/data/user/0/APP_NAME/app_flutter/someFolder', includeDirName: true) Woud store the files as:

someFolder/subFolder/file1.json
someFolder/file2.json

In most cases where you're manually adding directories, including the directory name makes sense; if your just zipping a directory by itself, it makes sense to not include the directory (like if you run gzip to compress a directory.)

Not that this is a great API, but I'm a bit worried about the workingDir solution being a bit specific.

the0neWhoKnocks commented 6 years ago

Fair enough, so long long as it maintains the proper file/folder structure I'm good :+1:

brendan-duncan commented 6 years ago

I pushed 2.0.2 with the updates, and included the basic usage in example.dart.

the0neWhoKnocks commented 6 years ago

Cool. Good work on this. Close this puppy out whenever you feel like it. :heavy_check_mark: