How to get the file path to an asset included in a Dart package? - dart

I am writing a Dart package (not Flutter). I have included a few bitmap images as public assets, e.g., lib/assets/empty.png. When this package is running as a command-line app for an end-user, how can I get the file path to these assets on the user's system?
Use-case: My Dart package calls out to FFMPEG, and I need to tell FFMPEG where to find these asset files on the system that's using my package. For example, the call to FFMPEG might look like:
ffmpeg -i "path/to/lib/assets/empty.png" ...

Accessing a Dart package's assets can happen in two modalities:
Running a Dart CLI app with the dart tool and accessing a dependency's assets, or
Running an executable CLI app
The difference between these two situations is that when you're running a CLI app using the dart tool, all of your dependencies are available as structured packages in a local cache on your system. However, when you're running an executable, all relevant code is compiled into a single binary, which means you no longer have access at runtime to your dependencies' packages, you only have access to your dependencies' tree-shaken, compiled code.
Accessing assets when running with dart
The following code will resolve a package asset URI to a file system path.
final packageUri = Uri.parse('package:your_package/your/asset/path/some_file.whatever');
final future = Isolate.resolvePackageUri(packageUri);
// waitFor is strongly discouraged in general, but it is accepted as the
// only reasonable way to load package assets outside of Flutter.
// ignore: deprecated_member_use
final absoluteUri = waitFor(future, timeout: const Duration(seconds: 5));
final file = File.fromUri(absoluteUri);
if (file.existsSync()) {
return file.path;
}
This resolution code was adapted from Tim Sneath's winmd package: https://github.com/timsneath/winmd/blob/main/lib/src/metadatastore.dart#L84-L106
Accessing assets when running an executable
When compiling a client app to an executable, that client app simply cannot access any asset files that were stored with the dependent package. However, there is a work around that may work for some people (it did for me). You can store Base64 encoded versions of your assets in your Dart code, within your package.
First, encode each of your assets into a Base64 string and store those strings somewhere in your Dart code.
const myAsset = "iVBORw0KGgoAAA....kJggg==";
Then, at runtime, decode the string back to bytes, and then write those bytes to a new file on the local file system. Here's the method I used in my case:
/// Writes this asset to a new file on the host's file system.
///
/// The file is written to [destinationDirectory], or the current
/// working directory, if no destination is provided.
String inflateToLocalFile([Directory? destinationDirectory]) {
final directory = destinationDirectory ?? Directory.current;
final file = File(directory.path + Platform.pathSeparator + fileName);
file.createSync(recursive: true);
final decodedBytes = base64Decode(base64encoded);
file.writeAsBytesSync(decodedBytes);
return file.path;
}
This approach was suggested by #passsy

Have a look at the dcli package.
It has a 'pack' command designed to solve exactly this problem.
It encodes assets into dart files that can be unpacked at runtime.

Related

Is there any way of loading local resource files in dart? (not flutter)

I want to load the bytes of a file into a variable while testing my flutter application.
I can't use the assets directory as those are bundled with the app and require WidgetsFlutterBinding.ensureInitialized();
I tried searching the file manually with the path package, but this did not seem to work and was rather hacky. That is why i'm searching for a more official approach.
I was thinking way to complicated ...
As Chuck Batson commented, you can just use the path from the projects root for passing it into the (dart:io) File:
File loadResource(String relativePath) {
final filePath = path.join("test", "resources", relativePath);
return File(filePath);
}
(Notice: The above code makes use of the path package for constructing a file path.)

Loading txt file as an asset for a dart package

In flutter it's easy to load a .txt asset at runtime by specifying it or its folder in the pubspec.yaml file and then loading it with rootBundle. However, i'm working on a pure dart package, and I'm struggling to work out how to get the package to load a .txt file relative to it's own directory structure.
When I use the package in a separate dart command line application i'm working on, the relative path that I specified in one of the package source code files causes an error to be thrown that the txt file doesn't exist. I understand why this error is being thrown, because the relative path is interpreted as being from the command line application's root directory instead of the package's root directory, but i'm unsure of how to solve this without specifying the absolute path for the .txt file. I'd rather not specify the absolute path as it makes the package less portable.
Is there anything similar to flutter's asset loading for a pure dart package?
I think you need the resolveSymbolicLinks or resolveSymbolicLinksSync methods to decode the relative path and then use the resolved path to read the txt file:
import 'dart:io';
void main() async {
String file = '../lib/main.dart';
var path = Uri.parse('.').resolveUri(Uri.file(file)).toFilePath();
print(path);
if (path == '') path = '.';
var resolved = await File(path).resolveSymbolicLinks();
print(resolved);
File(resolved).readAsString().then((String contents) {
print(contents);
});
}

Storing css an graphics outside .asar

I have an Electron App that - when used - needs to be packed as asar. On the other Hand CSS and graphics sometimes need to be changed while in use. Therefore I need to exclude some of the Files from packaging via --ignoreparameter and copy the unpacked Files manually into the Folder so I can change them easily. For that all of the Paths to my CSS need to be rewritten of course.
But then the App does not work in my development environment because those paths do not exist if not packed.
Does anybody know a Solution where I can access my CSS and graphic files in both environments - packed and unpacked?
You could use electron-is-dev to check if the app is running in a development environment or if it's in production. You would then use the file path that corresponds.
Something along the lines of:
const isDev = require('electron-is-dev')
if (isDev) {
//use development path (unpacked)
} else {
// use production path (packed)
}

dart pub build: exclude a file or directory

I am trying to exclude a list of files or directories when building a web application with dart's pub build.
Using this, as suggested by the documentation:
transformers:
- simple_transformer:
$exclude: "**/CVS"
does not work:
Error on line 10, column 3 of pubspec.yaml: "simple_transformer" is not a dependency.
- simple_transformer:
Is there a way to do it (using SDK 1.10.0) ?
Sadly there is currently no support to mark files as ignored by pub build as Günter already mentioned. The .gitignore feature was removed as it was undocumented and caused more trouble than it solved.
But you can execlude files from the build output. This means that the files are still processed (and still take time to process =/ ) but aren't present in the output directiory. This is useful for generating a deployable copy of your application in one go.
In our application we use a simple ConsumeTransformer to mark assets as consumed so that they are not written to the output folder:
library consume_transformer;
import 'package:barback/barback.dart';
class ConsumeTransformer extends Transformer implements LazyTransformer {
final List<RegExp> patterns = <RegExp>[];
ConsumeTransformer.asPlugin(BarbackSettings settings) {
if (settings.configuration['patterns'] != null) {
for (var pattern in settings.configuration['patterns']) {
patterns.add(new RegExp(pattern));
}
}
}
bool isPrimary(AssetId inputId) =>
patterns.any((p) => p.hasMatch(inputId.path));
void declareOutputs(DeclaringTransform transform) {}
void apply(Transform transform) => transform.consumePrimary();
}
The consumer requires a list of regex patterns as an argument an consumes the matched files. You need to add the transformer to you pubspec.yaml file as the last transformer:
transformers:
- ... # Your other transformers
- packagename/consume_transformer:
patterns: ["\\.psd$"]
The example configuration ignores all files that have the psd extension, but you can add pattern as you need them.
I created a pub package that contains the transformer, take a look here.
simple_transformer is the name of the transformer you want to inform to exclude the files. If you want to apply this to dart2js you need to use the name $dart2js instead of simple_transformer.
For more details about configuring $dart2js see https://www.dartlang.org/tools/pub/dart2js-transformer.html

How to auto run transformer on asset change

Barback package description:
An asset build system. Given a set of input files and a set of
transformations (think compilers, preprocessors and the like), will
automatically apply the appropriate transforms and generate output
files. When inputs are modified, automatically runs the transforms
that are affected. Runs transforms asynchronously and in parallel when
possible to maximize responsiveness.
Docs on assets and transformers say:
For pub serve, the transformers run when the dev server starts up and
whenever a source asset changes. The pub build command runs the
transformers once and then exits.
So I took this example:
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:barback/barback.dart';
import 'package:markdown/markdown.dart';
import 'dart:async';
class ConvertMarkdown extends Transformer {
// A constructor named "asPlugin" is required. It can be empty, but
// it must be present. It is how pub determines that you want this
// class to be publicly available as a loadable transformer plugin.
ConvertMarkdown.asPlugin();
// Any markdown file with one of the following extensions is
// converted to HTML.
String get allowedExtensions => ".md .markdown .mdown";
Future apply(Transform transform) {
return transform.primaryInput.readAsString().then((content) {
// The extension of the output is changed to ".html".
var id = transform.primaryInput.id.changeExtension(".html");
String newContent = "<html><body>"
+ markdownToHtml(content)
+ "</body></html>";
transform.addOutput(new Asset.fromString(id, newContent));
});
}
}
It runs as expected with pub build, but does nothing with pub serve except printing:
Build completed successfully
every time I change any file (not only appropriate asset) in the directory.
After reading this I think that Dart has some problems with watching files(not only directories) on Windows platform.
It is true that pub serve runs transformers after each file modification. But compared to pub build it is not outputting the results to the build/ folder. pub serve is a development server, so you need to request you assets via HTTP, for example in a browser.
See the pub serve documentation for more details

Resources