archive icon indicating copy to clipboard operation
archive copied to clipboard

ArchiveException: Could not find End of Central Directory Record

Open filipenanclarez opened this issue 6 years ago • 11 comments

E/flutter (18353): [ERROR:flutter/shell/common/shell.cc(181)] Dart Error: Unhandled exception: E/flutter (18353): ArchiveException: Could not find End of Central Directory Record E/flutter (18353): #0 ZipDirectory._findSignature (package:archive/src/zip/zip_directory.dart:145:5) E/flutter (18353): #1 new ZipDirectory.read (package:archive/src/zip/zip_directory.dart:27:20) E/flutter (18353): #2 ZipDecoder.decodeBuffer (package:archive/src/zip_decoder.dart:21:21) E/flutter (18353): #3 ZipDecoder.decodeBytes (package:archive/src/zip_decoder.dart:17:12) E/flutter (18353): #4 writeCounter (file:///C:/src/projetos/widget_dinamico/lib/main.dart:83:38) E/flutter (18353): E/flutter (18353): #5 main (file:///C:/src/projetos/widget_dinamico/lib/main.dart:133:1) E/flutter (18353): E/flutter (18353): #6 _startIsolate. (dart:isolate/runtime/libisolate_patch.dart:289:19) E/flutter (18353): #7 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:171:12)

I already do many checks of file type and/or content. I already criate a new ZIP file in computer, transfer to phone and test, and got same error.

My code is:

import 'dart:convert';

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

import 'package:path_provider/path_provider.dart';

import 'dart:io';

import 'package:flutter_document_picker/flutter_document_picker.dart';

import 'package:archive/archive.dart';
import 'package:archive/archive_io.dart';

const kAndroidUserAgent =
    'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';


  Future<String> loadAsset() async {
    return await rootBundle.loadString('assets/teste3.xhtml');
  }



final data = loadAsset();  

String selectedUrl;

String extPath; 

//recuperando localpath
Future<String> get _localPath async {
  final directory = await getApplicationDocumentsDirectory();
  print(directory.path);
  extPath = directory.path;
  return directory.path;
}

Future<File> get _localFile async {
  final path = await _localPath;
  return File('$path/teste.zip');
}

Future<File> writeCounter(String counter) async {
  
  final path = await FlutterDocumentPicker.openDocument();

  final ffile = await _localFile;
  
  File(path).copy(ffile.path);

  // Read the Zip file from disk.
  List<int> bytes = new File(ffile.path).readAsBytesSync();

  // Decode the Zip file
  Archive archive = new ZipDecoder().decodeBytes(bytes);

  // Extract the contents of the Zip archive to disk.
  for (ArchiveFile cfile in archive) {
    String filename = cfile.name;
    if (cfile.isFile) {
      List<int> cdata = cfile.content;
      new File('out/' + filename)
        ..createSync(recursive: true)
        ..writeAsBytesSync(cdata);
    } else {
      new Directory('out/' + filename)
        ..create(recursive: true);
    }
  }

  selectedUrl = extPath + '/out/OEBPS/1102014248.xhtml';

  // Write the file
  return File(extPath + '/out/OEBPS/1102014248.xhtml'); // file.writeAsString('$counter');
}



void main() async {

// escrevendo no arquivo
writeCounter(await data);

  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  final flutterWebviewPlugin = new FlutterWebviewPlugin();
  final _codeCtrl =
      new TextEditingController(text:  '' );   


  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter WebView Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      routes: {
        '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
        '/widget': (_) => new 
              WebviewScaffold( 
              url: selectedUrl,
              appBar: new AppBar(
                title: const Text('Widget webview'),
                actions: <Widget>[
                    new RaisedButton(
                      onPressed: () {



                      // escrevendo no arquivo
                      //data.then((String rres){writeCounter(rres);});

                      /* abrindo o epub */
                      //selectedUrl = "file:///data/user/0/com.example.widgetdinamico/app_flutter/teste.xhtml";


                      final future =
                          flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
                      
                      future.then( (String result) {
                        
                          //_history.add('eval: $result');
                          print('eval: $result');
                        
                      });


                      },
                      child: const Text('Color')
                    )
                ],
              ),
              clearCache: true,
              clearCookies: true,
              withZoom: false,
              withLocalStorage: true,
            )
      },
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // Instance of WebView plugin
  final flutterWebviewPlugin = new FlutterWebviewPlugin();

  // On destroy stream
  StreamSubscription _onDestroy;

  // On urlChanged stream
  StreamSubscription<String> _onUrlChanged;

  // On urlChanged stream
  StreamSubscription<WebViewStateChanged> _onStateChanged;

  StreamSubscription<WebViewHttpError> _onHttpError;

  StreamSubscription<double> _onScrollYChanged;

  StreamSubscription<double> _onScrollXChanged;

  final _urlCtrl = new TextEditingController(text: selectedUrl);

  final _codeCtrl =
      new TextEditingController(text: "confirm('teste');" /*'window.navigator.userAgent'*/);   /* \$('rb-f').css({"color": "red", "border": "2px solid red"}); */

  final _scaffoldKey = new GlobalKey<ScaffoldState>();

  final _history = [];

  @override
  void initState() {
    super.initState();

    flutterWebviewPlugin.close();

    _urlCtrl.addListener(() {
      selectedUrl = _urlCtrl.text;
    });

    // Add a listener to on destroy WebView, so you can make came actions.
    _onDestroy = flutterWebviewPlugin.onDestroy.listen((_) {
      if (mounted) {
        // Actions like show a info toast.
        _scaffoldKey.currentState.showSnackBar(
            const SnackBar(content: const Text('Webview Destroyed')));
      }
    });

    // Add a listener to on url changed
    _onUrlChanged = flutterWebviewPlugin.onUrlChanged.listen((String url) {
      print('_onUrlChanged');
      if (mounted) {
        setState(() {
          _history.add('onUrlChanged: $url');
        });
      }
    });

    _onScrollYChanged =
        flutterWebviewPlugin.onScrollYChanged.listen((double y) {
      if (mounted) {
        setState(() {
          _history.add("Scroll in  Y Direction: $y");
        });
      }
    });

    _onScrollXChanged =
        flutterWebviewPlugin.onScrollXChanged.listen((double x) {
      if (mounted) {
        setState(() {
          _history.add("Scroll in  X Direction: $x");
        });
      }
    });

    _onStateChanged =
        flutterWebviewPlugin.onStateChanged.listen((WebViewStateChanged state) {
          print('_onStateChanged');
      if (mounted) {
        setState(() {
          _history.add('onStateChanged: ${state.type} ${state.url}');
        });
      }
    });

    _onHttpError =
        flutterWebviewPlugin.onHttpError.listen((WebViewHttpError error) {
      if (mounted) {
        setState(() {
          _history.add('onHttpError: ${error.code} ${error.url}');
        });
      }
    });
  }

  @override
  void dispose() {
    // Every listener should be canceled, the same should be done with this stream.
    _onDestroy.cancel();
    _onUrlChanged.cancel();
    _onStateChanged.cancel();
    _onHttpError.cancel();
    _onScrollXChanged.cancel();
    _onScrollYChanged.cancel();

    flutterWebviewPlugin.dispose();

    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      key: _scaffoldKey,
      appBar: new AppBar(
        title: const Text('Plugin example app'),
      ),
      body: new Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          new Container(
            padding: const EdgeInsets.all(24.0),
            child: new TextField(controller: _urlCtrl),
          ),
          new RaisedButton(
            onPressed: () {
              flutterWebviewPlugin.launch(selectedUrl,
                  rect: new Rect.fromLTWH(
                      0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
                  userAgent: kAndroidUserAgent);
            },
            child: const Text('Open Webview (rect)'),
          ),
          new RaisedButton(
            onPressed: () {
              flutterWebviewPlugin.launch(selectedUrl, hidden: true);
            },
            child: const Text('Open "hidden" Webview'),
          ),
          new RaisedButton(
            onPressed: () {
              flutterWebviewPlugin.launch(selectedUrl);
            },
            child: const Text('Open Fullscreen Webview'),
          ),
          new RaisedButton(
            onPressed: () {
              flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
              Navigator.of(context).pushNamed('/widget');
            },
            child: const Text('Open widget webview'),
          ),
          new Container(
            padding: const EdgeInsets.all(24.0),
            child: new TextField(controller: _codeCtrl),
          ),
          new RaisedButton(
            onPressed: () {
              final future =
                  flutterWebviewPlugin.evalJavascript(_codeCtrl.text);
              future.then((String result) {
                setState(() {
                  _history.add('eval: $result');
                  print('eval: $result');
                });
              });
            },
            child: const Text('Eval some javascript'),
          ),
          new RaisedButton(
            onPressed: () {
              setState(() {
                _history.clear();
              });
              flutterWebviewPlugin.close();
            },
            child: const Text('Close'),
          ),
          new RaisedButton(
            onPressed: () {
              flutterWebviewPlugin.getCookies().then((m) {
                setState(() {
                  _history.add('cookies: $m');
                });
              });
            },
            child: const Text('Cookies'),
          ),
          new Text(_history.join('\n'))
        ],
      ),
    );
  }
}

filipenanclarez avatar Oct 05 '18 15:10 filipenanclarez

@brendan-duncan any updates regarding this issue?

Abgaryan avatar Oct 15 '18 14:10 Abgaryan

Sorry, I forgot to get back to this.

I haven't used Flutter and haven't done any Android development, so my ability to test my hypothesis is limited. That error indicates it can't find the byte tag in the file marking the end of the zip. Looking at your code, my suspicion is that because you do the following:

File(path).copy(ffile.path);
List<int> bytes = new File(ffile.path).readAsBytesSync();

File.copy is async, returns a Future, which means the next line that tries to read that copied file, it hasn't finished copying yet so it can't find the end marker. I would either wait for the future to complete before doing the rest of the decode, or use File.copySync.

brendan-duncan avatar Oct 16 '18 06:10 brendan-duncan

@brendan-duncan this my download function, which downloads the zip file from the Firebase Storage it works but time to time getting the error

Future<bool> downloadAssetBucket(
      {@required String categoryCode, @required firebaseConfig}) async {
   
 try {
      await _init(firebaseConfig: firebaseConfig);

      final Directory systemTempDir = Directory.systemTemp;

//    created theory directory if doesn't exists
      final Directory theoryDir = Directory('${systemTempDir.path}$theoryDirectoryName');
      if (!theoryDir.existsSync()) {
        await theoryDir.create();
      }

//    created category  directory if doesn't exists in theory  directory
      final Directory categoryDir = Directory('${theoryDir.path}${categoryCode}');

      if (!categoryDir.existsSync()) {
        await categoryDir.create();
      }

      final archiveName = "$categoryCode.$zipExtensionName";

      final StorageReference storageReference =
      await firebaseStorage.ref().child('$archiveBucketPath/$archiveName');

      final File assetsArchive = File('${categoryDir.path}/$archiveName');
      if (!assetsArchive.existsSync()) {
        await assetsArchive.create();
      }

      final StorageFileDownloadTask task = await storageReference.writeToFile(assetsArchive);
      await task;

      // Read the Zip file from cache.
      List<int> bytes = await assetsArchive.readAsBytes();

      // Decode the Zip file
      Archive archive = new ZipDecoder().decodeBytes(bytes);

      // Extract the contents of the Zip archive to  theory directory.

      for (ArchiveFile file in archive) {
        String filename = file.name;
        String assetPath = '${theoryDir.path}/$filename';
        if (file.isFile) {
          List<int> data = file.content;
          new File(assetPath)
            ..createSync(recursive: true)
            ..writeAsBytesSync(data);
        }
      }
     
      //remove the zip file after decoding
      assetsArchive.delete(recursive: true);
      return true;
    } catch (e) {
      rethrow;
    }
  }

Abgaryan avatar Oct 16 '18 13:10 Abgaryan

I don't see anything that stands out as obvious, so I'm not sure. The missing EOCD tag means it's not considered to be a valid zip, and if you know it's a valid zip, then something went wrong when reading the file, as in the first reported error. This is essentially what it's doing to find the EOCD tag in the zip file:

for (int i = bytes.length - 4; i >= 0; --i) {
  // ECOD = 0x06054b50
  if (bytes[i + 3] == 0x06 && bytes[i + 2] == 0x05 && bytes[i + 1] == 0x4b && bytes[i + 0] == 0x50) {
    print("EOCD FOUND!");
    break;
  }
}

If that isn't found, it's not a valid zip file.

brendan-duncan avatar Oct 18 '18 05:10 brendan-duncan

@brendan-duncan the file is zip and time to time it is downloading properly.

Abgaryan avatar Oct 20 '18 10:10 Abgaryan

Sorry, not much I can do to help with that. Sounds like a timing issue, like the file sometimes hasn't finished writing. Good luck.

brendan-duncan avatar Oct 21 '18 00:10 brendan-duncan

I just encountered the same error. Any updates?

HSCT avatar Jan 06 '20 04:01 HSCT

So no fix for this one yet?

nicowernli avatar Jul 15 '20 13:07 nicowernli

I got the same error when using latest version with decodeBuffer. To verify the issue, download sqlite zip file from https://sqlite.org/2021/sqlite-amalgamation-3360000.zip and then create Archive with decodeBuffer.

Update: using decodeBytes work perfectly.

ivacado avatar Oct 12 '21 16:10 ivacado

How are you calling decodeBuffer? decodeBytes is the same as decodeBuffer(InputStream(bytes)).

brendan-duncan avatar Oct 12 '21 19:10 brendan-duncan

same issue, any update on this?

sgsm74 avatar Dec 04 '21 07:12 sgsm74