1/*
2* Copyright (c) 2018 (https://github.com/phase1geo/Minder)
3*
4* This program is free software; you can redistribute it and/or
5* modify it under the terms of the GNU General Public
6* License as published by the Free Software Foundation; either
7* version 2 of the License, or (at your option) any later version.
8*
9* This program is distributed in the hope that it will be useful,
10* but WITHOUT ANY WARRANTY; without even the implied warranty of
11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12* General Public License for more details.
13*
14* You should have received a copy of the GNU General Public
15* License along with this program; if not, write to the
16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17* Boston, MA 02110-1301 USA
18*
19* Authored by: Trevor Williams <phase1geo@gmail.com>
20*/
21
22using Archive;
23
24public class ExportPortableMinder : Export {
25
26  /* Constructor */
27  public ExportPortableMinder() {
28    base( "portable-minder", _( "Portable Minder" ), { ".pminder" }, true, true );
29  }
30
31  /*
32   Exports the current mindmap along with all images to a single file that can
33   be imported in a different computer/location.
34  */
35  public override bool export( string fname, DrawArea da ) {
36
37    /* Create the tar.gz archive named according the the first argument */
38    Archive.Write archive = new Archive.Write ();
39    archive.add_filter_gzip();
40    archive.set_format_pax_restricted();
41    archive.open_filename( fname );
42
43    /* Add the Minder file to the archive */
44    archive_file( archive, da.get_doc().filename );
45
46    /* Add the images */
47    var image_ids = da.image_manager.get_ids();
48    for( int i=0; i<image_ids.length; i++ ) {
49      var id = image_ids.index( i );
50      archive_file( archive, da.image_manager.get_file( id ), id );
51    }
52
53    /* Close the archive */
54    if( archive.close() != Archive.Result.OK ) {
55      error( "Error : %s (%d)", archive.error_string(), archive.errno() );
56    }
57
58    return( true );
59
60  }
61
62  /* Adds the given file to the archive */
63  public bool archive_file( Archive.Write archive, string fname, int? image_id = null ) {
64
65    try {
66
67      var file              = GLib.File.new_for_path( fname );
68      var file_info         = file.query_info( GLib.FileAttribute.STANDARD_SIZE, GLib.FileQueryInfoFlags.NONE );
69      var input_stream      = file.read();
70      var data_input_stream = new DataInputStream( input_stream );
71
72      /* Add an entry to the archive */
73      var entry = new Archive.Entry();
74      entry.set_pathname( file.get_basename() );
75#if VALAC048
76      entry.set_size( (Archive.int64_t)file_info.get_size() );
77      entry.set_filetype( Archive.FileType.IFREG );
78      entry.set_perm( (Archive.FileMode)0644 );
79#else
80      entry.set_size( file_info.get_size() );
81      entry.set_filetype( (uint)Posix.S_IFREG );
82      entry.set_perm( 0644 );
83#endif
84
85      if( image_id != null ) {
86        entry.xattr_add_entry( "image_id", (void*)image_id, sizeof( int ) );
87      }
88
89      if( archive.write_header( entry ) != Archive.Result.OK ) {
90        critical ("Error writing '%s': %s (%d)", file.get_path (), archive.error_string (), archive.errno ());
91        return( false );
92      }
93
94      /* Add the actual content of the file */
95      size_t bytes_read;
96      uint8[] buffer = new uint8[64];
97      while( data_input_stream.read_all( buffer, out bytes_read ) ) {
98        if( bytes_read <= 0 ) {
99          break;
100        }
101#if VALAC048
102        archive.write_data( buffer );
103#else
104        archive.write_data( buffer, bytes_read );
105#endif
106      }
107
108    } catch( Error e ) {
109      critical( e.message );
110      return( false );
111    }
112
113    return( true );
114
115  }
116
117  //----------------------------------------------------------------------------
118
119  /*
120   Converts the portable Minder file into the Minder document and moves all
121   stored images to the ImageManager on the local computer.
122  */
123  public override bool import( string fname, DrawArea da ) {
124
125    Archive.Read archive = new Archive.Read();
126    archive.support_filter_gzip();
127    archive.support_format_all();
128
129    Archive.ExtractFlags flags;
130    flags  = Archive.ExtractFlags.TIME;
131    flags |= Archive.ExtractFlags.PERM;
132    flags |= Archive.ExtractFlags.ACL;
133    flags |= Archive.ExtractFlags.FFLAGS;
134
135    Archive.WriteDisk extractor = new Archive.WriteDisk();
136    extractor.set_options( flags );
137    extractor.set_standard_lookup();
138
139    /* Create the image directory */
140    string img_dir = ".";
141    try {
142      img_dir = DirUtils.make_tmp( "minder-images-XXXXXX" );
143    } catch( Error e ) {
144      critical( e.message );
145    }
146
147    /* Open the portable Minder file for reading */
148    if( archive.open_filename( fname, 16384 ) != Archive.Result.OK ) {
149      error( "Error: %s (%d)", archive.error_string(), archive.errno() );
150    }
151
152    string?               minder_path = null;
153    unowned Archive.Entry entry;
154
155    while( archive.next_header( out entry ) == Archive.Result.OK ) {
156
157      /*
158       We will need to modify the entry pathname so the file is written to the
159       proper location.
160      */
161      if( entry.pathname().has_suffix( ".minder" ) ) {
162        entry.set_pathname( fname.substring( 0, (fname.length - 8) ) + ".minder" );
163        minder_path = entry.pathname();
164      } else {
165        var file = File.new_for_path( Path.build_filename( img_dir, entry.pathname() ) );
166        entry.set_pathname( file.get_path() );
167      }
168
169      /* Read from the archive and write the files to disk */
170      if( extractor.write_header( entry ) != Archive.Result.OK ) {
171        continue;
172      }
173#if VALAC048
174      uint8[]         buffer;
175      Archive.int64_t offset;
176
177      while( archive.read_data_block( out buffer, out offset ) == Archive.Result.OK ) {
178        if( extractor.write_data_block( buffer, offset ) != Archive.Result.OK ) {
179          break;
180        }
181      }
182#else
183      void*       buffer = null;
184      size_t      buffer_length;
185      Posix.off_t offset;
186
187      while( archive.read_data_block( out buffer, out buffer_length, out offset ) == Archive.Result.OK ) {
188        if( extractor.write_data_block( buffer, buffer_length, offset ) != Archive.Result.OK ) {
189          break;
190        }
191      }
192#endif
193
194      /* If the file was an image file, make sure it gets added to the image manager */
195      if( !entry.pathname().has_suffix( ".minder" ) ) {
196        string name;
197        void*  value;
198        size_t size;
199        entry.xattr_reset();
200        if( (entry.xattr_next( out name, out value, out size ) == Archive.Result.OK) && (name == "image_id") ) {
201          int* id = (int*)value;
202          da.image_manager.add_image( "file://" + entry.pathname(), *id );
203        }
204      }
205
206    }
207
208    /* Close the archive */
209    if( archive.close () != Archive.Result.OK) {
210      error( "Error: %s (%d)", archive.error_string(), archive.errno() );
211    }
212
213    /* Delete the image directory */
214    DirUtils.remove( img_dir );
215
216    /* Finally, load the minder file and re-save it */
217    da.get_doc().load();
218    da.auto_save();
219
220    return( true );
221
222  }
223
224}
225