Skip to content

Commit 6e04a5b

Browse files
committed
Load examples also from ZIP files
1 parent 5d75124 commit 6e04a5b

4 files changed

Lines changed: 164 additions & 119 deletions

File tree

Controller.js

Lines changed: 150 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// (c) Marco Vieth, 2019
33
// https://benchmarko.github.io/CPCBasic/
44
//
5-
/* globals cpcBasicCharset Uint8Array */
5+
/* globals cpcBasicCharset ArrayBuffer, Uint8Array */
66

77
"use strict";
88

@@ -849,7 +849,7 @@ Controller.prototype = {
849849
iStartLine = 0,
850850
bPutInMemory = false,
851851
oData,
852-
sType;
852+
sType, aImported;
853853

854854
if (sInput !== null && sInput !== undefined) {
855855
oData = this.splitMeta(sInput);
@@ -871,6 +871,10 @@ Controller.prototype = {
871871
sInput = sInput.replace(/\x1a+$/, ""); // eslint-disable-line no-control-regex
872872
} else if (sType === "G") { // Hisoft Devpac GENA3 Z80 Assember
873873
sInput = this.asmGena3Convert(sInput);
874+
} else if (sType === "Z") { // ZIP file
875+
aImported = [];
876+
this.fnLoad2(sInput, oInFile.sName, sType, aImported);
877+
sInput = "1 ' " + aImported.join(", "); // imported files
874878
}
875879
}
876880

@@ -1906,15 +1910,147 @@ Controller.prototype = {
19061910
soundButton.innerText = sText;
19071911
},
19081912

1913+
1914+
processZipFile: function (uint8Array, name, aImported) {
1915+
var oZip, aZipDirectory, aEntries, i, sName2, sData2;
1916+
1917+
try {
1918+
oZip = new ZipFile(uint8Array, name);
1919+
} catch (e) {
1920+
Utils.console.error(e);
1921+
if (e instanceof Error) {
1922+
this.outputError(e, true);
1923+
}
1924+
}
1925+
if (oZip) {
1926+
aZipDirectory = oZip.getZipDirectory();
1927+
aEntries = Object.keys(aZipDirectory);
1928+
1929+
for (i = 0; i < aEntries.length; i += 1) {
1930+
sName2 = aEntries[i];
1931+
1932+
if (sName2.startsWith("__MACOSX/")) { // MacOS X creates some extra folder in ZIP files
1933+
Utils.console.log("processZipFile: Ignoring file:", sName2);
1934+
} else {
1935+
try {
1936+
sData2 = oZip.readData(sName2);
1937+
} catch (e) {
1938+
Utils.console.error(e);
1939+
if (e instanceof Error) { // eslint-disable-line max-depth
1940+
this.outputError(e, true);
1941+
}
1942+
}
1943+
1944+
if (sData2) {
1945+
this.fnLoad2(sData2, sName2, "", aImported, aImported); // type not known but without meta
1946+
}
1947+
}
1948+
}
1949+
}
1950+
},
1951+
1952+
processDskFile: function (data, sName, aImported) {
1953+
var oDsk, oDir, aDiskFiles, i, sFileName;
1954+
1955+
try {
1956+
oDsk = new DiskImage({
1957+
sData: data,
1958+
sDiskName: sName
1959+
});
1960+
oDir = oDsk.readDirectory();
1961+
aDiskFiles = Object.keys(oDir);
1962+
for (i = 0; i < aDiskFiles.length; i += 1) {
1963+
sFileName = aDiskFiles[i];
1964+
try { // eslint-disable-line max-depth
1965+
data = oDsk.readFile(oDir[sFileName]);
1966+
this.fnLoad2(data, sFileName, "cpcBasic/binary", aImported); // recursive
1967+
} catch (e) {
1968+
Utils.console.error(e);
1969+
this.outputError(e, true);
1970+
}
1971+
}
1972+
} catch (e) {
1973+
Utils.console.error(e);
1974+
this.outputError(e, true);
1975+
}
1976+
},
1977+
1978+
fnLoad2: function (data, sName, sType, aImported) { // eslint-disable-line complexity
1979+
var reRegExpIsText = new RegExp(/^\d+ |^[\t\r\n\x1a\x20-\x7e]*$/), // eslint-disable-line no-control-regex
1980+
// starting with (line) number, or 7 bit ASCII characters without control codes except \x1a=EOF
1981+
isStringData = typeof data === "string", // it is not: data instanceof Uint8Array or ArrayBuffer,
1982+
sStorageName, sMeta, iIndex, oHeader, sInfo1;
1983+
1984+
sStorageName = this.oVm.vmAdaptFilename(sName, "FILE");
1985+
sStorageName = this.fnLocalStorageName(sStorageName);
1986+
1987+
if (sType === "text/plain") {
1988+
oHeader = {
1989+
sType: "A",
1990+
iStart: 0,
1991+
iLength: data.length
1992+
};
1993+
} else {
1994+
if (sType === "application/x-zip-compressed" || sType === "cpcBasic/binary" || sType === "Z") { // are we a file inside zip?
1995+
// empty
1996+
} else if (isStringData && data.startsWith("data:application/octet-stream")) { // e.g. "data:application/octet-stream;base64,..."
1997+
iIndex = data.indexOf(",");
1998+
if (iIndex >= 0) {
1999+
sInfo1 = data.substr(0, iIndex);
2000+
data = data.substr(iIndex + 1); // remove meta prefix
2001+
if (sInfo1.indexOf("base64") >= 0) {
2002+
data = Utils.atob(data); // decode base64
2003+
}
2004+
}
2005+
}
2006+
2007+
oHeader = isStringData ? DiskImage.prototype.parseAmsdosHeader(data) : null;
2008+
if (oHeader) {
2009+
data = data.substr(0x80); // remove header
2010+
} else if (isStringData && DiskImage.prototype.testDiskIdent(data.substr(0, 8))) { // disk image file?
2011+
this.processDskFile(data, sName, aImported);
2012+
oHeader = null; // ignore dsk file
2013+
} else if (sType === "Z") {
2014+
this.processZipFile(isStringData ? Utils.string2Uint8Array(data) : data, sName, aImported);
2015+
} else if (reRegExpIsText.test(data)) {
2016+
oHeader = {
2017+
sType: "A",
2018+
iStart: 0,
2019+
iLength: data.length
2020+
};
2021+
} else { // binary
2022+
oHeader = {
2023+
sType: "B",
2024+
iStart: 0,
2025+
iLength: data.length
2026+
};
2027+
}
2028+
}
2029+
2030+
if (oHeader) {
2031+
sMeta = this.joinMeta(oHeader);
2032+
try {
2033+
Utils.localStorage.setItem(sStorageName, sMeta + "," + data);
2034+
this.updateStorageDatabase("set", sStorageName);
2035+
Utils.console.log("fnOnLoad: file: " + sStorageName + " meta: " + sMeta + " imported");
2036+
aImported.push(sName);
2037+
} catch (e) { // maybe quota exceeded
2038+
Utils.console.error(e);
2039+
if (e.name === "QuotaExceededError") {
2040+
e.shortMessage = sStorageName + ": Quota exceeded";
2041+
}
2042+
this.outputError(e, true);
2043+
}
2044+
}
2045+
},
2046+
2047+
19092048
// https://stackoverflow.com/questions/10261989/html5-javascript-drag-and-drop-file-from-external-window-windows-explorer
19102049
// https://www.w3.org/TR/file-upload/#dfn-filereader
19112050
fnHandleFileSelect: function (event) {
19122051
var aFiles = event.dataTransfer ? event.dataTransfer.files : event.target.files, // dataTransfer for drag&drop, target.files for file input
19132052
iFile = 0,
1914-
oStorage = Utils.localStorage,
19152053
that = this,
1916-
reRegExpIsText = new RegExp(/^\d+ |^[\t\r\n\x1a\x20-\x7e]*$/), // eslint-disable-line no-control-regex
1917-
// starting with (line) number, or 7 bit ASCII characters without control codes except \x1a=EOF
19182054
aImported = [],
19192055
f, oReader;
19202056

@@ -1940,7 +2076,7 @@ Controller.prototype = {
19402076
Utils.console.log(sText);
19412077
if (f.type === "text/plain") {
19422078
oReader.readAsText(f);
1943-
} else if (f.type === "application/x-zip-compressed") {
2079+
} else if (f.type === "application/x-zip-compressed" || f.type === "application/zip") { // on Mac OS it is "application/zip"
19442080
oReader.readAsArrayBuffer(f);
19452081
} else {
19462082
oReader.readAsDataURL(f);
@@ -1966,121 +2102,18 @@ Controller.prototype = {
19662102
fnReadNextFile();
19672103
}
19682104

1969-
function fnLoad2(sData, sName, sType) {
1970-
var sStorageName, sMeta, iIndex, oHeader,
1971-
oDsk, oDir, aDiskFiles, i, sFileName, sInfo1;
1972-
1973-
sStorageName = that.oVm.vmAdaptFilename(sName, "FILE");
1974-
sStorageName = that.fnLocalStorageName(sStorageName);
1975-
1976-
if (sType === "text/plain") {
1977-
oHeader = {
1978-
sType: "A",
1979-
iStart: 0,
1980-
iLength: sData.length
1981-
};
1982-
} else {
1983-
if (sType === "application/x-zip-compressed" || sType === "cpcBasic/binary") { // are we a file inside zip?
1984-
} else { // e.g. "data:application/octet-stream;base64,..."
1985-
iIndex = sData.indexOf(",");
1986-
if (iIndex >= 0) {
1987-
sInfo1 = sData.substr(0, iIndex);
1988-
sData = sData.substr(iIndex + 1); // remove meta prefix
1989-
if (sInfo1.indexOf("base64") >= 0) {
1990-
sData = Utils.atob(sData); // decode base64
1991-
}
1992-
}
1993-
}
1994-
1995-
oHeader = DiskImage.prototype.parseAmsdosHeader(sData);
1996-
if (oHeader) {
1997-
sData = sData.substr(0x80); // remove header
1998-
} else if (reRegExpIsText.test(sData)) {
1999-
oHeader = {
2000-
sType: "A",
2001-
iStart: 0,
2002-
iLength: sData.length
2003-
};
2004-
} else if (DiskImage.prototype.testDiskIdent(sData.substr(0, 8))) { // disk image file?
2005-
try {
2006-
oDsk = new DiskImage({
2007-
sData: sData,
2008-
sDiskName: sName
2009-
});
2010-
oDir = oDsk.readDirectory();
2011-
aDiskFiles = Object.keys(oDir);
2012-
for (i = 0; i < aDiskFiles.length; i += 1) {
2013-
sFileName = aDiskFiles[i];
2014-
try { // eslint-disable-line max-depth
2015-
sData = oDsk.readFile(oDir[sFileName]);
2016-
fnLoad2(sData, sFileName, "cpcBasic/binary"); // recursive
2017-
} catch (e) {
2018-
Utils.console.error(e);
2019-
that.outputError(e, true);
2020-
}
2021-
}
2022-
} catch (e) {
2023-
Utils.console.error(e);
2024-
that.outputError(e, true);
2025-
}
2026-
oHeader = null; // ignore dsk file
2027-
} else { // binary
2028-
oHeader = {
2029-
sType: "B",
2030-
iStart: 0,
2031-
iLength: sData.length
2032-
};
2033-
}
2034-
}
2035-
2036-
if (oHeader) {
2037-
sMeta = that.joinMeta(oHeader);
2038-
try {
2039-
oStorage.setItem(sStorageName, sMeta + "," + sData);
2040-
that.updateStorageDatabase("set", sStorageName);
2041-
Utils.console.log("fnOnLoad: file: " + sStorageName + " meta: " + sMeta + " imported");
2042-
aImported.push(sName);
2043-
} catch (e) { // maybe quota exceeded
2044-
Utils.console.error(e);
2045-
if (e.name === "QuotaExceededError") {
2046-
e.shortMessage = sStorageName + ": Quota exceeded";
2047-
}
2048-
that.outputError(e, true);
2049-
}
2050-
}
2051-
}
2052-
20532105
function fnOnLoad(evt) {
2054-
var sData = evt.target.result,
2106+
var data = evt.target.result,
20552107
sName = f.name,
2056-
sType = f.type,
2057-
oZip, aEntries, i;
2108+
sType = f.type;
20582109

2059-
if (sType === "application/x-zip-compressed") {
2060-
try {
2061-
oZip = new ZipFile(new Uint8Array(sData), sName); // rather aData
2062-
} catch (e) {
2063-
Utils.console.error(e);
2064-
that.outputError(e, true);
2065-
}
2066-
if (oZip) {
2067-
aEntries = Object.keys(oZip.oEntryTable);
2068-
for (i = 0; i < aEntries.length; i += 1) {
2069-
sName = aEntries[i];
2070-
try {
2071-
sData = oZip.readData(sName);
2072-
} catch (e) {
2073-
Utils.console.error(e);
2074-
that.outputError(e, true);
2075-
sData = null;
2076-
}
2077-
if (sData) {
2078-
fnLoad2(sData, sName, sType);
2079-
}
2080-
}
2081-
}
2110+
if ((sType === "application/x-zip-compressed" || sType === "application/zip") && data instanceof ArrayBuffer) { // on Mac OS it is "application/zip"
2111+
sType = "Z";
2112+
that.fnLoad2(new Uint8Array(data), sName, sType, aImported);
2113+
} else if (typeof data === "string") {
2114+
that.fnLoad2(data, sName, sType, aImported);
20822115
} else {
2083-
fnLoad2(sData, sName, sType);
2116+
Utils.console.warn("Error loading file", sName, "with type", sType, " unexpected data:", data);
20842117
}
20852118

20862119
fnReadNextFile();

Utils.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// (c) Marco Vieth, 2019
33
// https://benchmarko.github.io/CPCBasic/
44
//
5+
/* globals ArrayBuffer, Uint8Array */
56

67
"use strict";
78

@@ -145,6 +146,17 @@ var Utils = {
145146
atob: typeof window !== "undefined" && window.atob && window.atob.bind ? window.atob.bind(window) : null, // we need bind: https://stackoverflow.com/questions/9677985/uncaught-typeerror-illegal-invocation-in-chrome
146147
btoa: typeof window !== "undefined" && window.btoa && window.btoa.bind ? window.btoa.bind(window) : null,
147148

149+
string2Uint8Array: function (data) {
150+
var buf = new ArrayBuffer(data.length),
151+
view = new Uint8Array(buf),
152+
i;
153+
154+
for (i = 0; i < data.length; i += 1) {
155+
view[i] = data.charCodeAt(i);
156+
}
157+
return view;
158+
},
159+
148160
composeError: function (name, oError, message, value, pos, line, hidden) {
149161
var iEndPos;
150162

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0">
66
<link rel="stylesheet" href="cpcbasic.css" />
7-
<title id="title">CPC Basic v0.10.14</title>
7+
<title id="title">CPC Basic v0.10.15</title>
88
</head>
99

1010
<body id="pageBody">

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "cpcbasic",
3-
"version": "0.10.14",
3+
"version": "0.10.15",
44
"description": "# CPCBasic - Run CPC BASIC in a Browser",
55
"main": "cpcbasic.js",
66
"directories": {

0 commit comments

Comments
 (0)