The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Video::TeletextDB::Access - Represents Video::TeletextDB database access

SYNOPSIS

  use Video::TeletextDB;
  $tele_db      = Video::TeletextDB->new(...);
  $access       = $tele_db->access(...);

  $hash_ref     = $access->cache_status;

  $access->write_pages(%parameters);
  # Possible parameters are:
  # time  => $epoch_seconds
  # pages => \@pages
  $access->write_feed(%parameters);
  # Possible parameters are:
  # time  => $epoch_seconds
  # decoded_fields => \@decoded_fields
  @raw_pages    = $access->raw_fetch_page($page_nr, $subpage_nr);
  @pages        = $access->fetch_page_versions($page_nr, $subpage_nr);
  $page         = $access->fetch_page($page_nr, $subpage_nr);
  @page_ids     = $access->page_ids;
  @subpage_nrs  = $access->subpages($page_nr);
  $next_page_nr = $access->next_page($page_nr);
  $prev_page_nr = $access->prev_page($page_nr);

  $cache_dir    = $access->cache_dir;
  $channel      = $access->channel;
  $tele         = $access->teletext_db;
  $db           = $access->db;
  $db_file      = $access->db_file;
  $lock_file    = $access->lock_file;
  $access->lock;
  $page_versions= $access->page_versions;
  $umask        = $access->umask;
  $old_umask    = $access->umask($new_umask);
  $RW           = $access->RW;
  $old_RW       = $access->RW($new_RW);
  $user_data    = $access->user_data;
  $old_user_data= $access->user_data($new_user_data);

  $access->release;
  $db           = $access->acquire;
  $db           = $access->downgrade;
  $db           = $access->upgrade;
  $access->delete;

DESCRIPTION

This class implements the actual access to the database for a particular channel. It uses a Berkeley DB with an external lockfile for the actual storage.

METHODS

All methods throw an exception in case of failure unless mentioned otherwise.

All page and subpage numbers used in these methods are the real numbers. However, what's normally displayed on teletext viewers is the hexadecimal notation. So if you want to work with the page that's normally shown as page "100", you'll have to use 0x100 or 256 as an argument.

$hash_ref = $access->cache_status

Return a hashreference describing the last major update to the database, or undef if there hasn't been one yet. See write_pages for a more in depth discussion on what these values mean.

The hash keys are:

start_time

The storage time of the oldest page during the last major update.

end_time

The storage time of the most recent page during the last major update.

stores

The number of stores done during the last major update (the number of refreshed pages will normally be a lot lower than this because many of these stores will be for versions of the same page).

$access->write_pages(%parameters)

the arguments are a list of name/value pairs. Recognized are:

time => $epoch_seconds

This is the time that will be associated with all of the pages to be stored. If not given, will default to the current time.

When you use access to create a new Video::TeletextDB::Access object, an internal counter of number of stores is initialized to zero and internals start_time and end_time are set to undef. Whenever you then use this method to store at least one page, the stores counter is increased by the number of stored pages and the [start_time, end_time] interval is extended minimally to include the the page time.

Then it looks at the current values for these stored in the database. If there are none, it stores the internal values in the database. Otherwise it checks if the page time falls inside the database interval, and if so increases the database number of stores with the number of pages. Otherwise it looks if the internal number of stores is above a certain threshold (currently 10000) and if so writes internal start_time, end_time and number of stores to the database.

To make a long story short, the database values will now tell you the time period of the last major update to the database.

However, it will only work properly if you don't deallocate the Video::TeletextDB::Access object all the time. On the other hand, you don't want to keep the database open all the time since you will then lock out other users like the actual display program. So the proper action is to release the database when unneeded and to re-acquire it when it 's needed again.

pages => \@pages

A mandatory parameter refering to the pages that must get stored in the database. Each page itself must be a hash reference with the following values:

page => $nr

This is the page number (normally derived from packet 0)

ctrl => $value

The control flags for this page as a number (also normally derived from packet 0). Anding this with VTX_SUB (0x003f7f) should give the subpage number.

packet => \@packets

A reference to an array of packets associated with this page. Each packet is normally 40 bytes of raw teletext data. You may represent missing packets as an empty string or undef. A tail of missing packets on the list may be left out completely.

These fields are exactly in the format that's passed as argument to enter_page method of Video::Capture::VBI::VT. That means a basic teletext collector can look this:

    use Video::Capture::VBI;

    my $vbi_dev = "/dev/v4l/vbi0";

    package Decoder;
    use base 'Video::Capture::VBI::VT';

    my @pages;
    sub enter_page {
        push @pages, $_[1];
    }

    package main;
    my $vbi = Video::Capture::V4l::VBI->new($vbi_dev) ||
        die "Could not open $vbi_dev: $!";

    # max. 1 second backlog (~1M)
    $vbi->backlog(25);

    my $vt = Decoder->new;
    my $read_mask = "";
    vec($read_mask, $vbi->fileno, 1) = 1;

    my $tele = Video::TeletextDB->new(RW => 1, creat => 1);
    my $access = $tele->access(channel => "foo");
    $access->release;
    while (1) {
        # This select seems to be totally blocking
        select(my $r = $read_mask, undef, undef, undef);
        my $now = time;

        @pages = ();
        $vt->feed(decode_field $vbi->field, VBI_VT) while $vbi->queued;
        next unless @pages;

        $access->acquire;
        $access->write_pages(time => $now, pages => \@pages);
        $access->release;
    }
$access->write_feed(%parameters)

The page feeding in the example under write_pages is still a bit inconvenient, so the essential part of the Video::Capture::V4l::VBI feed code is duplicated here. It will take decoded fields, assemble them into pages and directly send them to the database by doing a write_pages. You should realize however that when it returns it can still internally store a currently unfinished page expecting that later decoded fields will finish that page. If it gets fed fields while there is nothing cached yet, it will drop fields until it sees a page start. So this is yet another reason to not drop the Video::TeletextDB::Access object all the time, but to only release the database.

The parameters are name/value pairs which can be:

time => $epoch_seconds

This is the time that will be associated with all of the pages to be stored. If not given, will default to the current time.

This will be passed to write_pages as the time parameter.

decoded_fields => \@decoded_fields

Mandatory parameter representing the already decoded fields that will get assembled into pages. All pages that are completed at the end of the method call will then be passed on as the pages parameter to write_pages.

Using this method the previous example to store a teletext feed in the database becomes:

    use Video::Capture::VBI;
    my $vbi_dev = "/dev/v4l/vbi0";

    package main;
    my $vbi = Video::Capture::V4l::VBI->new($vbi_dev) ||
        die "Could not open $vbi_dev: $!";

    # max. 1 second backlog (~1M)
    $vbi->backlog(25);

    my $read_mask = "";
    vec($read_mask, $vbi->fileno, 1) = 1;

    my $tele = Video::TeletextDB->new(RW => 1, creat => 1);
    my $access = $tele->access(channel => "foo");
    $access->release;
    while (1) {
        # This select seems to be totally blocking
        select(my $r = $read_mask, undef, undef, undef);
        my $now = time;

        my @decoded;
        push @decoded, decode_field($vbi->field, VBI_VT) while $vbi->queued;
        next unless @decoded;

        $access->acquire;
        $access->write_feed(time => $now, decoded_fields => \@decoded);
        $access->release;
    }
@raw_pages = $access->raw_fetch_page($page_nr, $subpage_nr)

Returns a list of all version of the given $page_nr/$subpage_nr in raw format (as stored in the database). The versions are sorted from most recent to oldest. Only the first page is guaranteed to be non-stale.

This function is meant for people who want to do their own processing of the raw data. Normally you'd use fetch_page or fetch_page_versions which are more cooked.

@pages = $access->fetch_page_versions($page_nr, $subpage_nr)

This has the same semantics as raw_fetch_page but each raw page is converted to a Video::TeletextDB::Page object to which you can apply the provided methods.

$page = $access->fetch_page($page_nr, $subpage_nr)

This is the preferred interface to fetch teletext pages from the database.

Internally this uses raw_fetch_page, but then mainly tries to return the most recent version of the page. It will however use the other versions of the pages in an attempt to fix up transmission errors. At the end one best effort Video::TeletextDB::Page is returned. Due to the use of older page versions you can in principle get an almost random mix of old and new information if there are errors in the first version. In practice things work quite well and you rarely see artifacts.

@page_ids = $access->page_ids

Returns a list of all decimal page ids in the database. A page id is of the form hex_page/hex_subpage. So if one of the returned strings is e.g. "100/10", that means that page 256, subpage 16 is in the database. The returned list is sorted, first by page and then by subpage.

@subpage_nrs = $access->subpages($page_nr)

Returns a list of subpages of the given page. It assumes that a subpage 0 and other non-0 subpages are mutually exclusive, so it returns only one of these two cases (the set that has the most recent subpage wins).

$next_page_nr = $access->next_page($page_nr)

Given a page number, it scans the database for the next existing non-hexadecimal page, wrapping back to the first page after the last. It can return the startpage itself if after a complete scan that's the only page it found. Returns undef if there are no non-hexadecimal pages.

$prev_page_nr = $access->prev_page($page_nr)

Given a page number, it scans the database for the previous existing non-hexadecimal page, wrapping back to the last page after the first. It can return the startpage itself if after a complete scan that's the only page it found. Returns undef if there are no non-hexadecimal pages.

$cache_dir = $access->cache_dir

Returns the directory containing the channel database. The same as

 $cache_dir = $access->tele_db->cache_dir
$channel = $access->channel

Returns the channel this $access object is associated with.

$tele = $access->teletext_db

Returns the Video::TeletextDB object that was used to create this $access object.

$db = $access->db

Return an open database handle if $access currently has one, false otherwise. The hanlde is a DB_File object to which you can apply the normal DB_File methods. There is no tied hash, so you can't use the tie interface.

$db_file = $access->db_file

Returns the name of the actual database file that is/will be used for the channel associated with the $access object.

$lock_file = $access->lock_file

Returns the name of the lockfile that is/will be used for the channel associated with the $access object.

$access->lock

Takes a blocking lock on $access->lock_file and truncates it to one line containing the process id ($$. Returns an open filehandle for that lockfile, which is the last reference, none is kept internally. So you'll have the lock for as long as you keep this handle alive, or until you do an explicit unlock.

You normally don't use this method since all locking is taken care of automatically by access, acquire and release.

$page_versions= $access->page_versions

Returns the value for page_versions. This normally can't be undef because if it starts out like that, it will be updated from the setting in the database, which itself is initialized with the current setting if there is none yet (and with $default_page_versions if that is undefined).

$umask = $access->umask

Returns the umask with which any lockfile or database creation will happen. A value of undef means that it will use the global umask at such a moment.

Remember that the returned value is a number, not a string with a leading 0.

$old_umask = $access->umask($new_umask)

Sets a new umask, and returns the old one.

Remember that an umask is a number, usually given in octal. It is not a string of octal digits. See oct to convert an octal string to a number.

$RW = $access->RW

Returns the current value of the RW parameter.

A true value means that any database open will by default be done in readwrite mode. The open database can later be switched to readonly by a downgrade.

An undefined value means the state of the database isn't fixed. It will start out as readonly on open, but whenever the system needs write access, it will internally do an upgrade.

All other false values (0 and "" normally) mean that any open will be readonly and is meant to remain so. An upgrade will fail unless you change the RW flag first.

The database needs some initialization on create though. So whatever value is returned here, there can be a little bit of write activity if the creat option is true (this can happen even if the database already existed if it somehow was missing its initial state). The database will then be reopened readonly if you wanted pure readonly access.

But whatever value this method returns, you can't conclude anything from that about the state of any currently opened database, since either the RW flag can have been changed since the open, or the database can have been upgraded or downgraded.

$old_RW = $access->RW($new_RW)

Sets a new value for the RW parameter, returning the old value.

$user_data = $access->user_data

With every TeletextDB::Access object you can associate one scalar of user data (default undef). This method returns that user data.

$old_user_data= $acess->user_data($new_user_data)

Set new user data, returning the old value.

$access->release

Closes access to the channel database and drops its lock. This will give other parties the chance to now acquire access, without you forgetting all attributes you've set. At a later time you can try to acquire the database again using the acquire method.

$db = $access->acquire

Will block until it can get a lock on the channel database and will then open the database, whose handle will then be returned. It will create lockfiles and database files if needed if the creat options is set.

$db = $access->downgrade

If you have readwrite access to the database, it will close it and then reopen it readonly. This is done without dropping the lock. The old database handle will be invalidated and the new database handle is returned.

If you already have readonly access to the database, it simply returns its handle.

All of this does in no way change the RW setting, so if that one is undef, methods will still upgrade to readwrite whenever needed. Use the RW method to change the RW setting if you don't want that.

$access->upgrade

If you have readonly access to the database, it will close it and reopen it readwrite, This is done without dropping the lock. The old database handle will be invalidated and the new database handle will be returned.

If you already have readwrite access to the database, it simply returns its handle.

All of this does in no way change the RW setting.

$access->delete

Closes and invalidates the database handle if it has the database open, erases the database and then deletes the lockfile. If any of these didn't actually exist that's not considered an error, the files are gone either way. (for the moment inability to remove the lockfile is also not considered an error).

EXPORT

None by default.

But you can ask for:

$expanded_path = tilde($path)

This does a ~ expansion like a typical unix shell does, though the rules are a bit simplified.

If $path begins with a tilde character (~) all of the following characters preceding the first slash (or all following characters, if there is no slash) are considered as a login name. If this login name is the empty string, the tilde is replaced with the value of the environment variable $ENV{HOME}. If HOME is unset, the home directory of the user executing the function is used instead. Otherwise, the tilde and the login name are replaced with the home directory associated with the specified login name, and the resulting string is returned.

Throws an exception if the specified user is not found or the home directory is an empty string.

If the $path doesn't start with a tilde, it's returned unchanged.

If you have a $path starting with a ~ and don't want it expanded, put <./> in front, e.g. ./~non-expanded instead of ~non-expanded.

$default_cache_dir

This is the cache directory that gets used if you give no cache_dir argument to the new method. It starts out as ~/.TeletextDB/cache.

$default_page_versions

The number that will be used for page_versions if there is none in the database yet and no setting is inherited from access.

DATABASE FORMAT

This section describes database format version 1. Normal users of this module shouldn't care about any of this, but may be needed if you want to write extensions.

The data are stored in a standard Berkeley BTREE database. Since it needs a number of logical tables, each logical key is prefixed with a single character saying which logical table it's for. The prefixes are available as names representing their meaning as the :BdbPrefixes tag in Video::TeletextDB::Constants.

All times in the database are in seconds since Jan 1 1970 without counting leap seconds, even if your systems epoch is not Jan 1 1970. The value of the standard Jan 1 1970 epoch in terms of the systems epoch is available in the exportable variable $Video::TeletextDB::Page::epoch_time.

The logical tables are:

VERSION

There is only a single entry with logical key "" and the version as a plain ASCII string (should be 1 for this specification). Only a freshly created database can have nothing there yet, in which case you should write one before writing anything into the database (see the internal db_check method which is automatically called for you already when use any of the documented methods to acquire a database).

PAGE_VERSIONS

Again only a single ebtry with logical key "" and a value in pack C format, representing how many versions of a given page will get stored. Only a freshly created database can have nothing there yet, in which case you should write one before writing anything into the database (see the internal db_check method which is automatically called for you already when use any of the documented methods to acquire a database).

STORES

Again there is only a single entry with logical key "" and a value in pack NNN format. These integers correspond to the values for end_time, stores and start_time in the cache_status method (except that all times are Jan 1 1970 based of course).

COUNTER

Maps a logical key in pack nn format (decimal page and subpage number) to CN format, where the first value is the entry number of the last page version written to the database. This will be an integer in the range [0..PAGE_VERSIONS[, which starts counting from 0, is increased by one on each new write and wraps back to 0 when it reaches PAGE_VERSIONS. The second number is the epoch time the version was extracted from the teletext stream.

There is a guaranteed terminator entry mapping "\xff" x 4 (invalid page 65535/65535) to "\x00" . "\xff" x 4 (version 0 written far into the future). Only a freshly created database can have nothing there yet, in which case you should write one before writing anything into the database (see the internal db_check method which is automatically called for you already when use any of the documented methods to acquire a database).

PAGE

Maps a logical key in pack nnC format (page and subpage number followed by verion number) to N(C/a*)* format. The first integer is again the epoch time the version was extracted from the teletext stream, followed by a sequence of counyed strings, each representing one decoded row packet (missing packets are represented by an empty string or simply dropped if they are at the end of the sequence).

Each COUNTER entry should always map to a valid PAGE entry, so you should write the PAGE entry before writing the corresponding COUNTER entry when storing a new page version, and delete the COUNTER entry before deleting all PAGE entries when removing a page completely.

There is a guaranteed terminator entry mapping "\xff" x 5 (invalid page 65535/65535 version 255) to "\xff" x 4 (empty page from far into the future). Only a freshly created database can have nothing there yet, in which case you should write one before writing anything into the database (see the internal db_check method which is automatically called for you already when use any of the documented methods to acquire a database).

SEE ALSO

DB_File, Video::TeletextDB, Video::TeletextDB::Page, Video::Capture::VBI,

AUTHOR

Ton Hospel, <Video-TeletextDB@ton.iguana.be>

COPYRIGHT AND LICENSE

Copyright (C) 2004 by Ton Hospel

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.4 or, at your option, any later version of Perl 5 you may have available.