plugins in SETUP_2_0...
Ryan C. Gordon
icculus at clutteredmind.org
Mon Jun 6 01:22:08 EDT 2005
I suspect Stephane is going to rip the UI a new asshole for 2.0, so I
spent some time thinking about the plugins instead.
Overall, I'm pleased with how the 1.0 archive system worked out in
principle, but in implementation it has a lot of the same problems as
the UI layer: code duplication, assumptions, and generic higher-level
burdens. There are various generic tags that each plugin is required to
cut and paste from existing plugins, and some do and some don't...plus
there are UI callbacks passed around, etc.
It is exceedingly difficult to implement a new plugin, even for people
that have done it before. When a new XML attribute is added to the
codebase, it requires editing each plugin to support generic
functionality. Working with the plugin code tends to be error-prone in
this respect.
So what we need, I think, is basically this:
1) Let's try to keep what's there intact in terms of functionality...we
have perfectly good zip, tar, rar, etc handlers, it doesn't make sense
to throw that existing code away entirely.
2) Let's try to have all the high-level code exist in the base
loki_setup, and make sure the plugins only deal with their archive. They
should do one thing well.
3) Let's have copying files and directory trees be a plugin so it's not
a special case anymore.
4) Let's get rid of the callbacks.
Basically, the plugins shouldn't be a one entry point "unpack this file
and get back to me" thing. Ideally, they should just look more or less
like the regular file code...the high level sees what files are there,
and then copies them, block by block.
The plugin interface then looks something like this (naming convention
is, of course, just an example here):
(these would be function pointers in a struct...)
First, find the plugin to handle a file by iterating over all plugins
with this method...
// Return non-zero if this is the .XXX plugin and this is really an .XXX
// file. Where possible, plugin should determine this by file signature
// (.zip files have end-of-central-directory, etc). Failing that, the
// plugin can report true or false based on the filename extension.
// In most cases, this would obsolete the "file" tag's "suffix"
// attribute.
int canHandleThisFile(const char *fname);
Once you find the right plugin, open the archive...
// prepare plugin to unpack this archive. Returns NULL on failure, an
// opaque data type on success.
void *openArchive(const char *fname);
Once the archive is opened, you iterate through its contents with
getNextItem() and readMoreData() until you are done...
// Get the next item from the archive. It may be a file, symlink, dir,
// or some other thing.
//
// PluginNextItemInfo is a struct like this:
// struct {
// const char *itemName; // name of item ("path/myfile.txt", etc)
// NextItemEnum itemType; // file, dir, symlink, etc.
// size_t itemSize; // total size of item (filesize, strlen of
symlink)
// };
//
// Some types need no further processing, such as directories; the
// higher level will know what to do with that immediately.
// Other things (symlinks, files) will need more reading with
// readMoreData().
//
// The plugin is expected to skip to the next valid item whether the
// current one was read or not, since the higher-level may choose to
// filter a given item out of the installation for various reasons (a
// symlink on windows? XML tags introduce filtering capabilities?)
//
// Returns 1 if there was another item, zero if EOF, -1 if error. Errors
// should be considered fatal to the installation, EOF should be the
// normal loop termination condition.
int getNextItem(void *handle, PluginNextItemInfo *nextItem);
// Read more data from the current item...for symlinks, this will be
// the name of the thing they point to, for files, this will be the
// uncompressed contents of the item. Data is stored at buf, a buffer
// of (*bufsize) bytes. On return, the number of bytes retrieved will
// be stored to (*bufsize), whether there was a problem or not.
//
// Returns 1 if there was data to read, zero if EOF, -1 if error. Errors
// should be considered fatal to the installation, EOF should be the
// normal loop termination condition. EOF only triggers when you try to
// read and there are no more bytes. A read that only fills one byte
// before hitting the EOF will still return 1, and the next call will
// return zero. This allows for archivers to return less than a full
// buffer each call if it's more convenient to their decompression
// algorithms.
int readMoreData(void *handle, void *buf, size_t *bufsize);
...finally, clean up...
void closeArchive(void *handle);
...you'll notice that the 1.0 plugin API has two methods: Size and Copy.
Size is intentionally removed...this was always less than useful (as
anyone that used it with a .tar.bz2 can tell you), and assumes you want
to install the whole archive, which may not be true in the future if new
XML tags show up to filter out items. Perhaps a better solution to this
is to assume the total size of an install is 0 bytes unless explicitly
stated using the existing XML attributes, and if the install doesn't
turn out to match the expected size (0 or otherwise), pop up a message
box saying so, and reporting the actual result, so that the developer
always fills this in correctly during final testing before shipping the
installer.
The rest of the new API is, more or less, a broken-out version of the
Copy() method, but with the control put into the caller's hands and
expecting the plugin to do its work in smaller chunks, so the caller can
handle the general tasks of updating the UI and doing the actual
installing of files from one unified place.
Comments?
--ryan.
More information about the Lokisetup
mailing list