Skip to content

Commit

Permalink
Implement platform file import/export features.
Browse files Browse the repository at this point in the history
  • Loading branch information
UtkarshSiddhpura committed Aug 16, 2024
1 parent 431e239 commit 9aa39c4
Show file tree
Hide file tree
Showing 8 changed files with 753 additions and 7 deletions.
2 changes: 1 addition & 1 deletion v2/js/modules/activities.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ define(["sugar-web/datastore"], function (datastore) {

// Test if activity is generic
activities.isGeneric = function(activity) {
return (activity == genericActivity);
return activity.directory === "icons" && activity.icon === "application-x-generic.svg";
};
activities.genericActivity = function() {
return genericActivity;
Expand Down
146 changes: 146 additions & 0 deletions v2/js/modules/file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
define(["platform/mobile", "platform/desktop", "platform/web"], function (
{ saveFileMobile, openDocInAndroid, openDocInIOS },
{ saveFileElectron, readFileElecton, openDocInElectron },
{ saveFileWeb, readFileWeb, openDocInWeb }
) {
const PLATFORM = sugarizer.getClientPlatform();
const saveFile = {
[sugarizer.constant.webAppType]: saveFileWeb,
[sugarizer.constant.mobileType]: saveFileMobile,
[sugarizer.constant.desktopAppType]: saveFileElectron,
};

const readFile = {
[sugarizer.constant.webAppType]: readFileWeb,
[sugarizer.constant.mobileType]: readFileWeb, //same works for mobile
[sugarizer.constant.desktopAppType]: readFileElecton,
};

const file = {};

// Write a new file
file.writeFile = async function (directory, metadata, content) {
let binary = null;
let text = null;
let extension = "json";
let title = metadata.title;
let mimetype = "application/json";

if (metadata && metadata.mimetype) {
mimetype = metadata.mimetype;
extension = getFileExtension(mimetype);
if (mimetype.startsWith("text/")) {
text = content;
} else {
binary = base64DecToArr(
content.substr(content.indexOf("base64,") + 7)
).buffer;
}
} else {
text = JSON.stringify({ metadata: metadata, text: content });
}

let filename = title;
if (!filename.endsWith(`.${extension}`)) {
filename += `.${extension}`;
}
await saveFile[PLATFORM]({
filename,
text,
binary,
mimetype,
directory,
});
return filename;
};

function getFileExtension(mimetype) {
const mimetypeExtensions = {
"image/jpeg": "jpg",
"image/png": "png",
"audio/wav": "wav",
"video/webm": "webm",
"audio/mp3": "mp3",
"audio/mpeg": "mp3",
"video/mp4": "mp4",
"text/plain": "txt",
"application/pdf": "pdf",
"application/msword": "doc",
"application/vnd.oasis.opendocument.text": "odt",
"text/csv": "csv",
};

return mimetypeExtensions[mimetype] || "bin";
}

function base64DecToArr(sBase64, nBlocksSize) {
var sB64Enc = sBase64.replace(/[^A-Za-z0-9\+\/]/g, ""),
nInLen = sB64Enc.length,
nOutLen = nBlocksSize
? Math.ceil(((nInLen * 3 + 1) >> 2) / nBlocksSize) * nBlocksSize
: (nInLen * 3 + 1) >> 2,
taBytes = new Uint8Array(nOutLen);
for (
var nMod3, nMod4, nUint24 = 0, nOutIdx = 0, nInIdx = 0;
nInIdx < nInLen;
nInIdx++
) {
nMod4 = nInIdx & 3;
nUint24 |=
b64ToUint6(sB64Enc.charCodeAt(nInIdx)) << (6 * (3 - nMod4));
if (nMod4 === 3 || nInLen - nInIdx === 1) {
for (
nMod3 = 0;
nMod3 < 3 && nOutIdx < nOutLen;
nMod3++, nOutIdx++
) {
taBytes[nOutIdx] =
(nUint24 >>> ((16 >>> nMod3) & 24)) & 255;
}
nUint24 = 0;
}
}
return taBytes;
}
// Decoding functions taken from
// https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding
function b64ToUint6(nChr) {
return nChr > 64 && nChr < 91
? nChr - 65
: nChr > 96 && nChr < 123
? nChr - 71
: nChr > 47 && nChr < 58
? nChr + 4
: nChr === 43
? 62
: nChr === 47
? 63
: 0;
}

// Ask the user a set files and write it to datastore
file.askAndReadFiles = async function () {
await readFile[PLATFORM]();
};

// Open the content as a document in a new Window
const openAsDocument = {
[sugarizer.constant.webAppType]: openDocInWeb,
[sugarizer.constant.mobileType]: function (metadata, text) {
if (
sugarizer.constant.platform.android ||
sugarizer.constant.platform.androidChrome
) {
openDocInAndroid(metadata, text);
} else {
openDocInIOS(metadata, text);
}
},
[sugarizer.constant.desktopAppType]: openDocInElectron,
};
file.openAsDocument = function (metadata, text) {
openAsDocument[PLATFORM](metadata, text);
};

return file;
});
110 changes: 110 additions & 0 deletions v2/js/modules/platform/desktop.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
define(function () {
function savefileElectron({
filename,
text,
binary,
mimetype,
extension,
directory,
}) {
return new Promise((resolve, reject) => {
ipcRenderer.removeAllListeners("save-file-reply");

ipcRenderer.send("save-file-dialog", {
directory,
filename,
mimetype,
extension,
text,
binary,
});

ipcRenderer.once("save-file-reply", (event, arg) => {
if (arg.err) {
reject(new Error(arg.err));
} else {
resolve();
}
});
});
}

function readFileElecton() {
const electron = require("electron");
const ipc = electron.ipcRenderer;

ipc.removeAllListeners("choose-files-reply");
ipc.send("choose-files-dialog");

return new Promise((resolve, reject) => {
ipc.once("choose-files-reply", (event, file, err, text) => {
if (err) {
resolve({ name: file.name, result: -1 });
} else {
writeFileToStore(file, text)
.then((result) => resolve({ name: file.name, result }))
.catch((error) => reject(error));
}
});
});
}

//helper func
function writeFileToStore(file, text, callback) {
if (file.type == "application/json") {
// Handle JSON file
var data = null;
try {
data = JSON.parse(text);
if (!data.metadata) {
callback(file.name, -1);
return;
}
} catch (e) {
callback(file.name, -1);
return;
}
callback(file.name, 0, data.metadata, data.text);
} else {
var activity = "";
var mimetype = file.type;
if (
file.type == "application/vnd.ms-excel" ||
file.type == "text/csv" ||
file.type == "text/x-csv" ||
file.type == "text/tab-separated-values" ||
file.type == "text/comma-separated-values"
) {
activity = "org.sugarlabs.ChartActivity";
mimetype = "text/csv";
} else if (
file.type != "text/plain" &&
file.type != "application/pdf" &&
file.type != "application/msword" &&
file.type != "application/vnd.oasis.opendocument.text"
) {
activity = "org.olpcfrance.MediaViewerActivity";
}
var metadata = {
title: file.name,
mimetype: mimetype,
activity: activity,
};
callback(file.name, 0, metadata, text);
}
}

function openDocInElectron(metadata, text) {
const electron = require("electron");
const shell = electron.shell;
const ipc = electron.ipcRenderer;

ipc.removeAllListeners("create-tempfile-reply");
ipc.send("create-tempfile", { metadata, text });
ipc.on("create-tempfile-reply", function (event, file) {
// Open in a shell
shell.openExternal("file://" + file);
});
}
return { savefileElectron, readFileElecton, openDocInElectron };
});
87 changes: 87 additions & 0 deletions v2/js/modules/platform/mobile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
define(function () {
async function saveFileMobile({ filename, text, binary, mimetype }) {
const cordovaFileStorage = platform.ios
? cordova.file.documentsDirectory
: cordova.file.externalRootDirectory;

try {
const directory = await new Promise((resolve, reject) => {
window.resolveLocalFileSystemURL(
cordovaFileStorage,
resolve,
reject
);
});

const file = await new Promise((resolve, reject) => {
directory.getFile(filename, { create: true }, resolve, reject);
});

const fileWriter = await new Promise((resolve, reject) => {
file.createWriter(resolve, reject);
});

fileWriter.seek(fileWriter.length);

await new Promise((resolve, reject) => {
fileWriter.onwriteend = resolve;
fileWriter.onerror = reject;

if (text) {
const blob = new Blob([text], { type: mimetype });
fileWriter.write(blob);
} else {
fileWriter.write(binary);
}
});

return file.fullPath;
} catch (error) {
throw new Error(`Failed to save file: ${error.message}`);
}
}

function openDocInAndroid(metadata, text) {
const cordovaFileStorage = enyo.platform.ios
? cordova.file.documentsDirectory
: cordova.file.externalCacheDirectory;

window.resolveLocalFileSystemURL(
cordovaFileStorage,
function (directory) {
directory.getFile(
"sugarizertemp",
{ create: true, exclusive: false },
function (file) {
if (!file) {
return;
}

file.createWriter(function (fileWriter) {
fileWriter.seek(fileWriter.length);
const blob = base64toBlob(metadata.mimetype, text);
fileWriter.write(blob);

// Open in the file system
const filename =
cordovaFileStorage + "sugarizertemp";
cordova.InAppBrowser.open(filename, "_system");
});
}
);
}
);
}

function openDocInIOS(metadata, text) {
const blob = base64toBlob(metadata.mimetype, text);
const blobUrl = URL.createObjectURL(blob);
cordova.InAppBrowser.open(
blobUrl,
"_blank",
"location=no,closebuttoncaption=" + l10n.get("Ok")
);
}

return { saveFileMobile, openDocInIOS, openDocInAndroid };
});
Loading

0 comments on commit 9aa39c4

Please sign in to comment.