slob_reader 0.1.6 copy "slob_reader: ^0.1.6" to clipboard
slob_reader: ^0.1.6 copied to clipboard

A pure Dart implementation of the Slob (Sorted List of Blobs) file format reader.

example/example.dart

// ignore_for_file: avoid_print
import 'package:slob_reader/slob_reader.dart';

/// Run with:
///   dart example/example.dart path/to/dictionary.slob
void main(List<String> args) async {
  final path = args.isNotEmpty ? args.first : 'test/abc.slob';

  print('Opening: $path\n');
  final reader = await SlobReader.open(path);

  // ─────────────────────────────────────────────────────────────
  // 1. Inspect file metadata via reader.header
  // ─────────────────────────────────────────────────────────────
  await _showMetadata(reader);

  // ─────────────────────────────────────────────────────────────
  // 2. Read a single entry by index — reader.getBlob(index)
  // ─────────────────────────────────────────────────────────────
  await _showSingleBlob(reader, index: 0);

  // ─────────────────────────────────────────────────────────────
  // 3. Read only the index record (no decompression) — reader.getRef(index)
  // ─────────────────────────────────────────────────────────────
  await _showRef(reader, index: 0);

  // ─────────────────────────────────────────────────────────────
  // 4. Read raw decompressed bytes — reader.getBlobContent(binIndex, itemIndex)
  // ─────────────────────────────────────────────────────────────
  await _showBlobContent(reader, index: 0);

  // ─────────────────────────────────────────────────────────────
  // 5. Batch read multiple ranges — reader.getBlobs(ranges)
  // ─────────────────────────────────────────────────────────────
  await _showBatchRead(reader);

  // ─────────────────────────────────────────────────────────────
  // 6. Binary search for a word using getRef
  // ─────────────────────────────────────────────────────────────
  await _binarySearch(reader, word: 'a');

  // ─────────────────────────────────────────────────────────────
  // 7. Enumerate all headwords without decompressing content
  // ─────────────────────────────────────────────────────────────
  await _listAllKeys(reader, limit: 10);

  // Always close when done.
  await reader.close();
  print('\nFile closed.');

  // ─────────────────────────────────────────────────────────────
  // 8. Open from a custom RandomAccessSource (e.g. for Android SAF)
  // ─────────────────────────────────────────────────────────────
  await _showCustomSource(path);

  print('\nBye!');
}

// ───────────────────────────────────────────────────────────────
// Section 8 — Custom Source
// ───────────────────────────────────────────────────────────────
Future<void> _showCustomSource(String path) async {
  print('══════════════════════════════════');
  print(' 8. Custom Source (RandomAccessSource)');
  print('══════════════════════════════════');

  // Here we use FileRandomAccessSource which implements RandomAccessSource.
  // In a real Flutter app (hdict), you would use SafStreamSource.
  final source = FileRandomAccessSource(path);
  print('Opening via source: ${source.path}');
  
  final reader = await SlobReader.openSource(source);
  print('Successfully opened source.');
  print('Entry count: ${reader.header.blobCount}');

  await reader.close();
  print('Custom source closed.');
}

// ───────────────────────────────────────────────────────────────
// Section 1 — Metadata
// ───────────────────────────────────────────────────────────────
Future<void> _showMetadata(SlobReader reader) async {
  print('══════════════════════════════════');
  print(' 1. File Metadata (reader.header)');
  print('══════════════════════════════════');

  final h = reader.header;
  print('UUID:          ${h.uuid}');
  print('Encoding:      ${h.encoding}');
  print('Compression:   "${h.compression}"');
  print('Total entries: ${h.blobCount}');
  print('File size:     ${h.size} bytes');

  if (h.tags.isNotEmpty) {
    print('\nTags:');
    h.tags.forEach((key, value) => print('  $key = $value'));
  }

  print('\nContent Types:');
  for (var i = 0; i < h.contentTypes.length; i++) {
    print('  [$i] ${h.contentTypes[i]}');
  }
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 2 — Single entry (getBlob)
// ───────────────────────────────────────────────────────────────
Future<void> _showSingleBlob(SlobReader reader, {required int index}) async {
  print('══════════════════════════════════');
  print(' 2. Single Entry  reader.getBlob($index)');
  print('══════════════════════════════════');

  final blob = await reader.getBlob(index);

  print('id:           ${blob.id}');
  print('key:          "${blob.key}"');
  print('fragment:     "${blob.fragment}"');
  print('contentType:  "${blob.contentType}"');
  print('content size: ${blob.content.length} bytes');

  // Only print text content to avoid noise for binary blobs.
  if (blob.contentType.startsWith('text/')) {
    final text = String.fromCharCodes(blob.content);
    final preview = text.length > 300 ? '${text.substring(0, 300)}…' : text;
    print('\nContent preview:\n$preview');
  }
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 3 — Index entry only (getRef)
// ───────────────────────────────────────────────────────────────
Future<void> _showRef(SlobReader reader, {required int index}) async {
  print('══════════════════════════════════');
  print(' 3. Index Entry  reader.getRef($index)');
  print('══════════════════════════════════');

  final ref = await reader.getRef(index);

  print('key:       "${ref.key}"');
  print('binIndex:  ${ref.binIndex}');
  print('itemIndex: ${ref.itemIndex}');
  print('fragment:  "${ref.fragment}"');
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 4 — Raw content bytes (getBlobContent)
// ───────────────────────────────────────────────────────────────
Future<void> _showBlobContent(SlobReader reader, {required int index}) async {
  print('══════════════════════════════════');
  print(' 4. Raw Bytes  reader.getBlobContent(binIndex, itemIndex)');
  print('══════════════════════════════════');

  final ref = await reader.getRef(index);
  final bytes = await reader.getBlobContent(ref.binIndex, ref.itemIndex);

  print('For key "${ref.key}" → bin=${ref.binIndex}, item=${ref.itemIndex}');
  print('Decompressed size: ${bytes.length} bytes');
  // Show first 16 bytes as hex
  final hex = bytes
      .take(16)
      .map((b) => b.toRadixString(16).padLeft(2, '0'))
      .join(' ');
  print('First 16 bytes:    $hex');
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 5 — Batch read (getBlobs)
// ───────────────────────────────────────────────────────────────
Future<void> _showBatchRead(SlobReader reader) async {
  print('══════════════════════════════════');
  print(' 5. Batch Read  reader.getBlobs(ranges)');
  print('══════════════════════════════════');

  final total = reader.header.blobCount;

  // Range 1: first 3 entries.
  // Range 2: last 2 entries (or fewer if total < 5).
  final range1Length = total.clamp(0, 3);
  final range2Start = (total - 2).clamp(0, total);
  final range2Length = total - range2Start;

  final ranges = [
    (0, range1Length),
    if (range2Length > 0) (range2Start, range2Length),
  ];

  print('Reading ranges: $ranges');
  final blobs = await reader.getBlobs(ranges);

  print('Got ${blobs.length} blobs:');
  for (final blob in blobs) {
    print('  [${blob.id}] "${blob.key}"  (${blob.content.length} bytes, ${blob.contentType})');
  }
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 6 — Binary search with getRef
// ───────────────────────────────────────────────────────────────
Future<void> _binarySearch(SlobReader reader, {required String word}) async {
  print('══════════════════════════════════');
  print(' 6. Binary Search for "$word"');
  print('══════════════════════════════════');

  var lo = 0;
  var hi = reader.header.blobCount - 1;
  SlobBlob? found;

  while (lo <= hi) {
    final mid = (lo + hi) ~/ 2;
    final blob = await reader.getBlob(mid);
    final cmp = blob.key.toLowerCase().compareTo(word.toLowerCase());
    if (cmp == 0) {
      found = blob;
      break;
    } else if (cmp < 0) {
      lo = mid + 1;
    } else {
      hi = mid - 1;
    }
  }

  if (found != null) {
    print('✅ Found: "${found.key}" (${found.content.length} bytes)');
  } else {
    print('❌ "$word" not found in this file.');
  }
  print('');
}

// ───────────────────────────────────────────────────────────────
// Section 7 — List all keys using getRef (no decompression)
// ───────────────────────────────────────────────────────────────
Future<void> _listAllKeys(SlobReader reader, {required int limit}) async {
  print('══════════════════════════════════');
  print(' 7. All Headwords  reader.getRef(i)  [first $limit]');
  print('══════════════════════════════════');

  final count = reader.header.blobCount.clamp(0, limit);
  for (var i = 0; i < count; i++) {
    final ref = await reader.getRef(i);
    final frag = ref.fragment.isNotEmpty ? '#${ref.fragment}' : '';
    print('  [$i] ${ref.key}$frag');
  }

  if (reader.header.blobCount > limit) {
    print('  … (${reader.header.blobCount - limit} more entries)');
  }
  print('');
}
0
likes
160
points
363
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A pure Dart implementation of the Slob (Sorted List of Blobs) file format reader.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

archive, dictzip_reader

More

Packages that depend on slob_reader