This is a continuation of linked question.
It seems to me, that current implementation of the std/archive/tar.ts module only allows reads and writes per file and not for whole directories.
So far, my reference source are the test files, which only show case single file processing. But what if, for example, a directory ./my-dir/ with multiple files and a tar archive ./test.tar is given.
How can I then utilize append, extract & Co. to efficiently write ./my-dir/ to ./test.tar archive and read all file contents back from it?
You can archive a directory by using std/fs/walk
import { walk, walkSync } from "https://deno.land/std/fs/walk.ts";
import { Tar } from "https://deno.land/std/archive/tar.ts";
// Async
const tar = new Tar();
for await (const entry of walk("./dir-to-archive")) {
if (!entry.isFile) {
continue;
}
await tar.append(entry.path, {
filePath: entry.path,
});
}
const writer = await Deno.open("./out.tar", { write: true, create: true });
await Deno.copy(tar.getReader(), writer);
Untar implementation for folders/multiple files was broken, it was fixed by this PR and currently available in master using https://deno.land/std/archive/tar.ts
import { Untar } from "https://deno.land/std/archive/tar.ts";
import { ensureFile } from "https://deno.land/std/fs/ensure_file.ts";
import { ensureDir } from "https://deno.land/std/fs/ensure_dir.ts";
const reader = await Deno.open("./out.tar", { read: true });
const untar = new Untar(reader);
for await (const entry of untar) {
console.log(entry); // metadata
/*
fileName: "archive/deno.txt",
fileMode: 33204,
mtime: 1591657305,
uid: 0,
gid: 0,
size: 24400,
type: 'file'
*/
if (entry.type === "directory") {
await ensureDir(entry.fileName);
continue;
}
await ensureFile(entry.fileName);
const file = await Deno.open(entry.fileName, { write: true });
// <entry> is a reader
await Deno.copy(entry, file);
}
reader.close();
Update
Created a lib to allow transformations, including gzip/gunzip to create & read .tar.gz
gzip
import * as Transform from "https://deno.land/x/transform/mod.ts";
const { GzEncoder } = Transform.Transformers;
/** ... **/
const writer = await Deno.open("./out.tar.gz", { write: true, create: true });
await Transform.pipeline(tar.getReader(), new GzEncoder())
.to(writer);
writer.close();
gunzip
import * as Transform from "https://deno.land/x/transform/mod.ts";
const { GzDecoder } = Transform.Transformers;
/** ... **/
const reader = await Deno.open("./out.tar.gz", { read: true });
const untar = new Untar(
Transform.newReader(input, new GzDecoder())
);
for await (const entry of untar) {
console.log(entry);
}
Related
I'm trying to figure why I keep getting the following erorr with this code
[uncaught application error]: Error - checksum error
import { Untar } from "https://deno.land/std#0.128.0/archive/tar.ts";
import { readerFromStreamReader } from "https://deno.land/std#0.128.0/streams/conversion.ts";
const res = await fetch("https://registry.npmjs.org/react/-/react-17.0.2.tgz", { keepalive: true });
if (res.status === 200) {
const streamReader = res.body!.getReader();
const reader = readerFromStreamReader(streamReader);
const untar = new Untar(reader);
for await (const block of untar) {
// errors with [uncaught application error]: Error - checksum error
}
}
Can you Untar from a stream like this?
The response you are streaming is compressed with gzip compression, so you need to pipe the stream data through a decompression transform stream first:
./so-71365204.ts
import {
assertExists,
assertStrictEquals,
} from "https://deno.land/std#0.128.0/testing/asserts.ts";
import { readerFromStreamReader } from "https://deno.land/std#0.128.0/streams/conversion.ts";
import { Untar } from "https://deno.land/std#0.128.0/archive/tar.ts";
const res = await fetch("https://registry.npmjs.org/react/-/react-17.0.2.tgz");
assertStrictEquals(res.status, 200);
assertExists(res.body);
const streamReader = res.body
.pipeThrough(new DecompressionStream("gzip"))
.getReader();
const denoReader = readerFromStreamReader(streamReader);
const untar = new Untar(denoReader);
for await (const entry of untar) {
const { fileName, type } = entry;
console.log(type, fileName);
}
$ deno run --allow-net=registry.npmjs.org ./so-71365204.ts
file package/LICENSE
file package/index.js
file package/jsx-dev-runtime.js
# etc...
I'm using firebase/storage to set up audio file downloading/uploading. I have the audio file in my firestore storage already.
With the following code, I am able to get the download URL of the specific file:
import firebase from 'firebase/app';
import 'firebase/firestore';
import 'firebase/storage';
static async downloadMedia(mediaRef: string) {
try {
var storage = firebase.storage();
var pathReference = storage.ref(mediaRef);
const downloadUrl = await pathReference.getDownloadURL();
var xhr = new XMLHttpRequest();
xhr.responseType = 'blob';
xhr.onload = (event) => {
var blob = xhr.response;
};
xhr.open('GET', downloadUrl);
return downloadUrl;
} catch (e) {
switch (e.code) {
case 'storage/object-not-found':
console.warn('File does not exist.');
break;
case 'storage/unauthorized':
console.warn('Unauthorized.');
break;
case 'storage/canceled':
console.warn('Upload cancelled.');
break;
case 'storage/unknown':
console.warn('Unknown error.');
break;
}
}
}
However, I do not understand how to use the firebase library to download the file itself with the URL that it provides me.
Thanks.
Found a solution which doesn't involve me downloading the media but instead playing it directly with the download URL.
Using package 'expo-av'.
Hope this helps someone in my shoes!
export default function AudioPlay({ mediaDownloadUrl } : AudioPlayProps) {
const [sound, setSound] = React.useState<Audio.Sound | null>(null);
async function playSound() {
if (typeof mediaDownloadUrl !== 'string') return null;
try {
const { sound } = await Audio.Sound.createAsync(
{ uri: mediaDownloadUrl }
);
setSound(sound);
await sound.playAsync();
} catch (e) {
console.warn(e);
}
}
React.useEffect(() => {
return sound
? () => {
console.log('Unloading Sound');
sound.unloadAsync(); }
: undefined;
}, [sound]);
// ....
How do I convert a file to Readable stream ?
I am trying to use deno's fetch api to do this, which requires a readable stream as body to put something on server.
I am not able to figure out how to convert a file to ReadableStream ?
There isn't a built-in way yet to convert a Reader to a ReadableStream.
You can convert it using the following code:
const file = await Deno.open("./some-file.txt", { read: true });
const stream = new ReadableStream({
async pull(controller) {
try {
const b = new Uint8Array(1024 * 32);
const result = await file.read(b);
if (result === null) {
controller.close();
return file.close();
}
controller.enqueue(b.subarray(0, result));
} catch (e) {
controller.error(e);
controller.close();
file.close();
}
},
cancel() {
// When reader.cancel() is called
file.close();
},
});
// ReadableStream implements asyncIterator
for await (const chunk of stream) {
console.log(chunk);
}
Have in mind that Deno (1.0.5) fetch does not yet support a ReadableStream as request body.
So currently to post a file, you'll need to buffer the contents.
const body = await Deno.readAll(file);
await fetch('http://example.com', { method: 'POST', body });
I've been using basic async/await for some time without many problems and I thought I understood how it worked. Can't say I'm an expert in it, but I understadn the gist of it. I just can't get my head around Streams though. Before today I thought I understood how they worked (basically ala Reactive Programming), but I can't get them to work in Dart.
I'm working on a persistance layer with the possibility of saving and retrieving (json) files. I've been using the fileManager example as a guideline.
import 'dart:io';
import 'dart:async';
import 'package:intl/intl.dart'; //date
import 'package:markdowneditor/model/note.dart';//Model
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:flutter/foundation.dart'; //log
import 'package:simple_permissions/simple_permissions.dart';//OS permissions
class FileManager {
static final FileManager _singleton = new FileManager._internal();
factory FileManager() {
return _singleton;
}
FileManager._internal();
Future<String> get _localPath async {
final directory = (await getApplicationDocumentsDirectory()).toString();
return p.join(directory, "notes"); //path takes strings and not Path objects
}
Future<File> writeNote(Note note) async {
var file = await _localPath;
file = p.join(
file,
DateFormat('kk:mm:ssEEEMMd').format(DateTime.now()) +
" " +
note.title); //add timestamp to title
// Write the file
SimplePermissions.requestPermission(Permission.WriteExternalStorage)
.then((value) {
if (value == PermissionStatus.authorized) {
return File(file).writeAsString('$note');
} else {
SimplePermissions.openSettings();
return null;
}
});
}
Future<List<Note>> getNotes() async {
//need file access permission on android. use https://pub.dartlang.org/packages/simple_permissions#-example-tab-
final file = await _localPath;
SimplePermissions.requestPermission(Permission.ReadExternalStorage)
.then((value) {
if (value == PermissionStatus.authorized) {
try {
Stream<FileSystemEntity> fileList =
Directory(file).list(recursive: false, followLinks: false);
// await for(FileSystemEntity s in fileList) { print(s); }
List<Note> array = [];
fileList.forEach((x) {
if (x is File) {
var res1 = ((x as File).readAsString()).then((value2) {
Note note = Note.fromJsonResponse(value2);
return note;
}).catchError((error) {
debugPrint('is not file content futurestring getNoteError: $x');
return null;
});
var array2 = res1.then((value3) {
array.add(value3);
return array;
});
//?
} else {
debugPrint('is not file getNoteError: $x');
}
});
// Add the file to the files array
//Return the Future<List<Note>>
return array;
} catch (e) {
debugPrint('getNoteError: $e');
// If encountering an error, return 0
return null;
}
} else {
SimplePermissions.openSettings();
return null;
}
});
}
}
Obviously as it is it won't work, but even trying to await the loop using the commented out parts raises an error.
In "getNotes", after checking the permissions I want to get an array of all the files in the directory, parse them as Note objects and return the resulting array.
I get the list of files:
Stream<FileSystemEntity> fileList =
Directory(file).list(recursive: false, followLinks: false);
And for each one of them in the stream I want to parse the file into an object and append it to an array to return at the end.
List<Note> array = [];
fileList.forEach((x) {
if (x is File) {
var res1 = ((x as File).readAsString()).then((value2) {
Note note = Note.fromJsonResponse(value2);
return note;
}).catchError((error) {
debugPrint('is not file content futurestring getNoteError: $x');
return null;
});
var array2 = res1.then((value3) {
array.add(value3);
return array;
});
//?
} else {
debugPrint('is not file getNoteError: $x');
}
});
// Add the file to the files array
//Return the Future<List<Note>>
return array;
Stream.forEach() returns a Future. Your last return statement runs immediately after the for-each call, but should await it.
await fileList.forEach((x) {
...
https://api.dartlang.org/stable/2.2.0/dart-async/Stream/forEach.html
I have to send a file to an API, therefor I have to use fs.readFileSync(). After uploading the picture to the storage, I am calling my function to execute the API call. But I cannot get the file from the storage. This is a section of the code, which always gets null in the result. I tried also to .getFiles() without a parameter and then I got all files but I dont want to filter them by iteration.
exports.stripe_uploadIDs = functions.https //.region("europe-west1")
.onCall((data, context) => {
const authID = context.auth.uid;
console.log("request is authentificated? :" + authID);
if (!authID) {
throw new functions.https.HttpsError("not authorized", "not authorized");
}
let accountID;
let result_fileUpload;
let tempFile = path.join(os.tmpdir(), "id_front.jpg");
const options_id_front_jpeg = {
prefix: "/user/" + authID + "/id_front.jpg"
};
const storageRef = admin
.storage()
.bucket()
.getFiles(options_id_front)
.then(results => {
console.log("JPG" + JSON.stringify(results));
// need to write this file to tempFile
return results;
});
const paymentRef = storageRef.then(() => {
return admin
.database()
.ref("Payment/" + authID)
.child("accountID")
.once("value");
});
const setAccountID = paymentRef.then(snap => {
accountID = snap.val();
return accountID;
});
const fileUpload = setAccountID.then(() => {
return Stripe.fileUploads.create(
{
purpose: "identity_document",
file: {
data: tempFile, // Documentation says I should use fs.readFileSync("filepath")
name: "id_front.jpg",
type: "application/octet-stream"
}
},
{ stripe_account: accountID }
);
});
const fileResult = fileUpload.then(result => {
result_fileUpload = result;
console.log(JSON.stringify(result_fileUpload));
return result_fileUpload;
});
return fileResult;
});
Result is:
JPG[[]]
You need to download your file from a bucket to your local function context env.
After your Firebase function start executing you can call the below:
More or less the below should work, just tweak to your needs. Call this within you .onCall context, you get the idea
import admin from 'firebase-admin';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
admin.initializeApp();
const { log } = console;
async function tempFile(fileBucket: string, filePath: string) {
const bucket = admin.storage().bucket(fileBucket);
const fileName = 'MyFile.ext';
const tempFilePath = path.join(os.tmpdir(), fileName);
const metadata = {
contentType: 'DONT_FORGET_CONTEN_TYPE'
};
// Donwload the file to a local temp file
// Do whatever you need with it
await bucket.file(filePath).download({ destination: tempFilePath });
log('File downloaded to', tempFilePath);
// After you done and if you need to upload the modified file back to your
// bucket then uploaded
// This is optional
await bucket.upload(tempFilePath, {
destination: filePath,
metadata: metadata
});
//free up disk space by realseasing the file.
// Otherwise you might be charged extra for keeping memory space
return fs.unlinkSync(tempFilePath);
}