Better handling of IO errors when reading/writing PGN files

This commit is contained in:
Peter Osterlund 2019-09-27 21:26:41 +02:00
parent 913cb0d9db
commit 3ff78c2cbf
4 changed files with 68 additions and 73 deletions

View File

@ -18,8 +18,6 @@
package org.petero.droidfish.gamelogic; package org.petero.droidfish.gamelogic;
import android.util.Pair;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
@ -28,7 +26,6 @@ import java.util.ArrayList;
import org.petero.droidfish.DroidFishApp; import org.petero.droidfish.DroidFishApp;
import org.petero.droidfish.activities.PGNFile; import org.petero.droidfish.activities.PGNFile;
import org.petero.droidfish.activities.PGNFile.GameInfo; import org.petero.droidfish.activities.PGNFile.GameInfo;
import org.petero.droidfish.activities.PGNFile.GameInfoResult;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -47,9 +44,7 @@ public class PGNFileTest extends TestCase {
}; };
writeFile(f, lines); writeFile(f, lines);
PGNFile pgnFile = new PGNFile(f.getAbsolutePath()); PGNFile pgnFile = new PGNFile(f.getAbsolutePath());
Pair<GameInfoResult,ArrayList<GameInfo>> res = pgnFile.getGameInfo(null, null); ArrayList<GameInfo> gi = pgnFile.getGameInfo(null, null);
assertEquals(GameInfoResult.OK, res.first);
ArrayList<GameInfo> gi = res.second;
assertEquals(1, gi.size()); assertEquals(1, gi.size());
assertEquals(0, gi.get(0).startPos); assertEquals(0, gi.get(0).startPos);
assertEquals(14, gi.get(0).endPos); assertEquals(14, gi.get(0).endPos);
@ -97,9 +92,7 @@ public class PGNFileTest extends TestCase {
}; };
writeFile(f, lines); writeFile(f, lines);
PGNFile pgnFile = new PGNFile(f.getAbsolutePath()); PGNFile pgnFile = new PGNFile(f.getAbsolutePath());
Pair<GameInfoResult,ArrayList<GameInfo>> res = pgnFile.getGameInfo(null, null); ArrayList<GameInfo> gi = pgnFile.getGameInfo(null, null);
assertEquals(GameInfoResult.OK, res.first);
ArrayList<GameInfo> gi = res.second;
assertEquals(2, gi.size()); assertEquals(2, gi.size());
assertEquals(0, gi.get(0).startPos); assertEquals(0, gi.get(0).startPos);
assertEquals(660, gi.get(0).endPos); assertEquals(660, gi.get(0).endPos);
@ -127,9 +120,7 @@ public class PGNFileTest extends TestCase {
}; };
writeFile(f, lines); writeFile(f, lines);
PGNFile pgnFile = new PGNFile(f.getAbsolutePath()); PGNFile pgnFile = new PGNFile(f.getAbsolutePath());
Pair<GameInfoResult,ArrayList<GameInfo>> res = pgnFile.getGameInfo(null, null); ArrayList<GameInfo> gi = pgnFile.getGameInfo(null, null);
assertEquals(GameInfoResult.OK, res.first);
ArrayList<GameInfo> gi = res.second;
assertEquals(2, gi.size()); assertEquals(2, gi.size());
assertEquals(4, gi.get(0).startPos); assertEquals(4, gi.get(0).startPos);
assertEquals(80, gi.get(0).endPos); assertEquals(80, gi.get(0).endPos);
@ -138,9 +129,8 @@ public class PGNFileTest extends TestCase {
assertEquals(137, gi.get(1).endPos); assertEquals(137, gi.get(1).endPos);
assertEquals("2. w - b 1-0", gi.get(1).info); assertEquals("2. w - b 1-0", gi.get(1).info);
res = pgnFile.getGameInfo(1); gi = pgnFile.getGameInfo(1);
assertEquals(GameInfoResult.OK, res.first); assertEquals(1, gi.size());
assertEquals(1, res.second.size());
} }
} }

View File

@ -46,7 +46,6 @@ import org.petero.droidfish.activities.LoadFEN;
import org.petero.droidfish.activities.LoadScid; import org.petero.droidfish.activities.LoadScid;
import org.petero.droidfish.activities.PGNFile; import org.petero.droidfish.activities.PGNFile;
import org.petero.droidfish.activities.PGNFile.GameInfo; import org.petero.droidfish.activities.PGNFile.GameInfo;
import org.petero.droidfish.activities.PGNFile.GameInfoResult;
import org.petero.droidfish.activities.Preferences; import org.petero.droidfish.activities.Preferences;
import org.petero.droidfish.book.BookOptions; import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.engine.EngineUtil; import org.petero.droidfish.engine.EngineUtil;
@ -798,8 +797,13 @@ public class DroidFish extends Activity
} }
PGNFile pgnFile = new PGNFile(fn); PGNFile pgnFile = new PGNFile(fn);
long fileLen = FileUtil.getFileLength(fn); long fileLen = FileUtil.getFileLength(fn);
Pair<GameInfoResult,ArrayList<GameInfo>> gi = pgnFile.getGameInfo(2); boolean moreThanOneGame = false;
if ((fileLen > 1024 * 1024) || (gi.first == GameInfoResult.OK && gi.second.size() > 1)) { try {
ArrayList<GameInfo> gi = pgnFile.getGameInfo(2);
moreThanOneGame = gi.size() > 1;
} catch (IOException ignore) {
}
if (fileLen > 1024 * 1024 || moreThanOneGame) {
filename = fn; filename = fn;
} else { } else {
try (FileInputStream in = new FileInputStream(fn)) { try (FileInputStream in = new FileInputStream(fn)) {
@ -2288,8 +2292,8 @@ public class DroidFish extends Activity
fenPgn.append(clip.getItemAt(i).coerceToText(getApplicationContext())); fenPgn.append(clip.getItemAt(i).coerceToText(getApplicationContext()));
try { try {
String fenPgnData = fenPgn.toString(); String fenPgnData = fenPgn.toString();
Pair<GameInfoResult,ArrayList<GameInfo>> gi = PGNFile.getGameInfo(fenPgnData, 2); ArrayList<GameInfo> gi = PGNFile.getGameInfo(fenPgnData, 2);
if (gi.first == GameInfoResult.OK && gi.second.size() > 1) { if (gi.size() > 1) {
String sep = File.separator; String sep = File.separator;
String fn = Environment.getExternalStorageDirectory() + sep + String fn = Environment.getExternalStorageDirectory() + sep +
pgnDir + sep + ".sharedfile.pgn"; pgnDir + sep + ".sharedfile.pgn";

View File

@ -50,10 +50,11 @@ import org.petero.droidfish.ObjectCache;
import org.petero.droidfish.R; import org.petero.droidfish.R;
import org.petero.droidfish.Util; import org.petero.droidfish.Util;
import org.petero.droidfish.activities.PGNFile.GameInfo; import org.petero.droidfish.activities.PGNFile.GameInfo;
import org.petero.droidfish.activities.PGNFile.GameInfoResult;
import org.petero.droidfish.databinding.SelectGameBinding; import org.petero.droidfish.databinding.SelectGameBinding;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Locale; import java.util.Locale;
@ -180,7 +181,7 @@ public abstract class EditPGN extends AppCompatActivity {
if (canceled) { if (canceled) {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
finish(); finish();
} else if (gamesInFile.size() == 0) { } else if (gamesInFile.isEmpty()) {
pgnFile.appendPGN(pgnToSave); pgnFile.appendPGN(pgnToSave);
finish(); finish();
} else { } else {
@ -431,29 +432,33 @@ public abstract class EditPGN extends AppCompatActivity {
long modTime = new File(fileName).lastModified(); long modTime = new File(fileName).lastModified();
if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName)) if (cacheValid && (modTime == lastModTime) && fileName.equals(lastFileName))
return true; return true;
Pair<GameInfoResult, ArrayList<GameInfo>> p = pgnFile.getGameInfo(this, progress); try {
if (p.first != GameInfoResult.OK) { gamesInFile = pgnFile.getGameInfo(this, progress);
gamesInFile = new ArrayList<>(); cacheValid = true;
switch (p.first) { lastModTime = modTime;
case OUT_OF_MEMORY: lastFileName = fileName;
runOnUiThread(() -> DroidFishApp.toast(R.string.file_too_large, Toast.LENGTH_SHORT)); return true;
break; } catch (PGNFile.CancelException ignore) {
case NOT_PGN: } catch (PGNFile.NotPgnFile ex) {
runOnUiThread(() -> DroidFishApp.toast(R.string.not_a_pgn_file, Toast.LENGTH_SHORT)); runOnUiThread(() -> DroidFishApp.toast(R.string.not_a_pgn_file,
break; Toast.LENGTH_SHORT));
case CANCEL: } catch (FileNotFoundException ex) {
case OK: if (!loadGame) {
break; gamesInFile = new ArrayList<>();
return true;
} }
setResult(RESULT_CANCELED); runOnUiThread(() -> DroidFishApp.toast(ex.getMessage(),
finish(); Toast.LENGTH_LONG));
return false; } catch (IOException ex) {
runOnUiThread(() -> DroidFishApp.toast(ex.getMessage(),
Toast.LENGTH_LONG));
} catch (OutOfMemoryError ex) {
runOnUiThread(() -> DroidFishApp.toast(R.string.file_too_large,
Toast.LENGTH_SHORT));
} }
gamesInFile = p.second; setResult(RESULT_CANCELED);
cacheValid = true; finish();
lastModTime = modTime; return false;
lastFileName = fileName;
return true;
} }
private void sendBackResult(GameInfo gi) { private void sendBackResult(GameInfo gi) {

View File

@ -33,7 +33,6 @@ import org.petero.droidfish.R;
import android.app.Activity; import android.app.Activity;
import android.app.ProgressDialog; import android.app.ProgressDialog;
import android.util.Pair;
import android.widget.Toast; import android.widget.Toast;
public class PGNFile { public class PGNFile {
@ -111,13 +110,6 @@ public class PGNFile {
} }
} }
public static enum GameInfoResult {
OK,
CANCEL,
NOT_PGN,
OUT_OF_MEMORY;
}
private static class BytesToString { private static class BytesToString {
private byte[] buf = new byte[256]; private byte[] buf = new byte[256];
private int len = 0; private int len = 0;
@ -185,9 +177,21 @@ public class PGNFile {
} }
} }
public static class NotPgnFile extends IOException {
NotPgnFile() {
super("");
}
}
public static class CancelException extends IOException {
CancelException() {
super("");
}
}
/** Return info about all PGN games in a file. */ /** Return info about all PGN games in a file. */
public Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfo(Activity activity, public ArrayList<GameInfo> getGameInfo(Activity activity,
ProgressDialog progress) { ProgressDialog progress) throws IOException {
if (activity == null || progress == null) if (activity == null || progress == null)
return getGameInfoFromFile(null, -1); return getGameInfoFromFile(null, -1);
ProgressHandler handler = new ProgressHandler(fileName, activity, progress); ProgressHandler handler = new ProgressHandler(fileName, activity, progress);
@ -195,41 +199,36 @@ public class PGNFile {
} }
/** Return info about up to "maxGames" PGN games in a file. */ /** Return info about up to "maxGames" PGN games in a file. */
public Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfo(int maxGames) { public ArrayList<GameInfo> getGameInfo(int maxGames) throws IOException {
return getGameInfoFromFile(null, maxGames); return getGameInfoFromFile(null, maxGames);
} }
public static Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfo(String pgnData, public static ArrayList<GameInfo> getGameInfo(String pgnData, int maxGames) {
int maxGames) {
try (InputStream is = new ByteArrayInputStream(pgnData.getBytes("UTF-8"))) { try (InputStream is = new ByteArrayInputStream(pgnData.getBytes("UTF-8"))) {
return getGameInfo(is, null, maxGames); return getGameInfo(is, null, maxGames);
} catch (IOException ex) { } catch (IOException ex) {
return new Pair<>(GameInfoResult.NOT_PGN, null); return new ArrayList<>();
} }
} }
private Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfoFromFile(ProgressHandler progress, private ArrayList<GameInfo> getGameInfoFromFile(ProgressHandler progress,
int maxGames) { int maxGames) throws IOException {
try (InputStream is = new FileInputStream(fileName)) { try (InputStream is = new FileInputStream(fileName)) {
return getGameInfo(is, progress, maxGames); return getGameInfo(is, progress, maxGames);
} catch (IOException ex) {
return new Pair<>(GameInfoResult.NOT_PGN, null);
} }
} }
/** Return info about PGN games in a file. */ /** Return info about PGN games in a file. */
private static Pair<GameInfoResult,ArrayList<GameInfo>> getGameInfo(InputStream is, private static ArrayList<GameInfo> getGameInfo(InputStream is, ProgressHandler progress,
ProgressHandler progress, int maxGames) throws IOException {
int maxGames) {
ArrayList<GameInfo> gamesInFile = new ArrayList<>(); ArrayList<GameInfo> gamesInFile = new ArrayList<>();
long nRead = 0;
try (BufferedInput f = new BufferedInput(is)) { try (BufferedInput f = new BufferedInput(is)) {
GameInfo gi = null; GameInfo gi = null;
HeaderInfo hi = null; HeaderInfo hi = null;
boolean inHeader = false; boolean inHeader = false;
boolean inHeaderSection = false; boolean inHeaderSection = false;
long filePos = 0; long filePos = 0;
long nRead = 0;
int gameNo = 1; int gameNo = 1;
final int INITIAL = 0; final int INITIAL = 0;
@ -383,7 +382,7 @@ public class PGNFile {
if (progress != null) if (progress != null)
progress.reportProgress(filePos); progress.reportProgress(filePos);
if (Thread.currentThread().isInterrupted()) if (Thread.currentThread().isInterrupted())
return new Pair<>(GameInfoResult.CANCEL, null); throw new CancelException();
} }
gi = new GameInfo(); gi = new GameInfo();
gi.startPos = filePos; gi.startPos = filePos;
@ -397,14 +396,11 @@ public class PGNFile {
gi.info = hi.toString(); gi.info = hi.toString();
gamesInFile.add(gi); gamesInFile.add(gi);
} }
} catch (IOException ignore) {
} catch (OutOfMemoryError e) {
return new Pair<>(GameInfoResult.OUT_OF_MEMORY, null);
} }
if (gamesInFile.isEmpty()) if (gamesInFile.isEmpty() && nRead > 1)
return new Pair<>(GameInfoResult.NOT_PGN, null); throw new NotPgnFile();
return new Pair<>(GameInfoResult.OK, gamesInFile); return gamesInFile;
} }
private void mkDirs() { private void mkDirs() {