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