Search code examples
rubytarstringio

Appending files to a StringIO Tar archive one at a time


I am writing code that utilizes the StringIO object and rubygems/package which includes TarWriter and TarReader.

My end goal is to be able to call a method add_file to add/append a file to the archive and then call a method read_all_files to read back the file names and contents of the added files.

My two methods currently stand as:

require "rubygems/package"

def add_file(io)
  Gem::Package::TarWriter.new(io) do |tar|
    puts "What is the name of the file you wish to add?"
    print "> "
    filename = gets.chomp
    puts

    tar.add_file(filename, 0755) do |file|
      puts "Enter the file contents"
      print "> "
      contents = gets.chomp

      file.write contents
    end
  end
end

def read_all_files(io)
  Gem::Package::TarReader.new(io) do |tar|
    tar.each do |tarfile|
      puts "File Name> #{tarfile.full_name}"
      puts tarfile.read
    end
  end
end

#usage:

io = StringIO.new

add_file(io)
add_file(io)
add_file(io)
io.rewind
read_all_files(io)

Output:

What is the name of the file you wish to add?
> test1

Enter the file contents
> this is the first test
What is the name of the file you wish to add?
> test2

Enter the file contents
> this is the second test
What is the name of the file you wish to add?
> test3

Enter the file contents
> this is the third test
File Name> test1
this is the first test

The problem that is currently happening is that for one reason or another read_all_files is only reading a single file although I should be iterating all three files.

I have tried various ideas such as rewinding the file after each add_file call, but this overwrites the tar file each time. I have also tried to seek to the end of the io object and add the files but this also does not function correctly.

I initially thought that TarWriter would read the header and automatically append new files to the tar archive in the correct position, but it seems that this is not the case.

This seems as if I will need to read all files from the StringIO tar archive to variables and then re-add all the files each time a new file is added. Is this proper behavior? Is there a way around this?

Thanks


Solution

  • The problem is because you are creating a new TarWriter each time you call add_file. You need to preserve the TarWriter object between calls to add_file. e.g.

    require "rubygems/package"
    
    def add_file(io, tar=nil)
    
      tar = Gem::Package::TarWriter.new(io) if tar.nil?
    
      puts "What is the name of the file you wish to add?"
      print "> "
      filename = gets.chomp
      puts
    
      tar.add_file(filename, 0755) do |file|
        puts "Enter the file contents"
        print "> "
        contents = gets.chomp
    
        file.write contents
      end
    
      tar
    end
    
    def read_all_files(io)
      Gem::Package::TarReader.new(io) do |tar|
        puts 'TarReader created'
        tar.each do |tarfile|
          puts "File Name> #{tarfile.full_name}"
          puts tarfile.read
        end
      end
    end
    
    io = StringIO.new
    
    tar1 = add_file(io)
    tar1 = add_file(io, tar1)
    tar1 = add_file(io, tar1)
    io.rewind
    read_all_files(io)
    

    The output from this:

       C:\Users\Administrator\test>ruby tarwriter.rb
       What is the name of the file you wish to add?
       > x.txt
       Enter the file contents
       > xxx
       What is the name of the file you wish to add?
       > z.txt
       Enter the file contents
       > zzz
       What is the name of the file you wish to add?
       > y.txt
       Enter the file contents
       > yyy
       TarReader created
       File Name> x.txt
       xxx
       File Name> z.txt
       zzz
       File Name> y.txt
       yyy