Revision 64 (by gdshaw@RISCPKG.ORG, 2007/05/21 05:44:33) Disabled exceptions and enabled optimisation to reduce code size.
// This file is part of the free Filer module for RISC OS.
// Copyright � 2007 Graham Shaw.
// Redistribution and modification are permitted under the terms of the
// GNU General Public License (version 2 or any later version).

#include <cstring>
#include <cstdio>
#include <new>

#include "oslib/os.h"
#include "oslib/osfscontrol.h"

#include "pathname_hash.h"
#include "pathname_table.h"
#include "filer_window.h"

// Rules for ensuring thread safety:
// - the UpCall handler may search the index at any time (therefore _size
//   and the content of _index must be valid at all times).
// - the UpCall handler may assume that the link referred to by _tail is
//   always null.  It may change that link from null to non-null at any
//   time, provided that it first moves _tail to a different null link.

/** Upcall handler entry point.
 * The interface is as described in the PRM.
 */
extern "C" void upcall_entry();

pathname_table::pathname_table(int* pollword):
	_size(0),
	_capacity(0),
	_index_data(0),
	_index_nodes(0),
	_tail(&_head),
	_pollword(pollword)
{
	// Initialise refresh queue.
	_head.next=0;

	// Initialise free list.
	_free.next=0;

	// Activate upcall handler.
	xos_claim(UpCallV,(void*)upcall_entry,(byte*)this);
}

pathname_table::~pathname_table()
{
	// Deactivate upcall handler.
	xos_release(UpCallV,(void*)upcall_entry,(byte*)this);

	// Deallocate nodes in index.
	for (unsigned int i=0;i!=_size;++i)
	{
		delete _index_nodes[i];
	}

	// Deallocate nodes in free list.
	while (_free.next)
	{
		node* n=_free.next;
		_free.next=n->next;
		delete n;
	}

	// Deallocate index.
	delete[] _index_data;
	delete[] _index_nodes;
}

void pathname_table::reserve(size_type capacity)
{
	// Each allocation is processed separately in order to avoid
	// a memory leak of one of them fails.
	if (_capacity<capacity)
	{
		// Reallocate index data (without allowing the existing index
		// to become invalid until it is no longer needed).
		int* new_index_data=new(std::nothrow) int[capacity];
		for (unsigned int i=0;i!=_size;++i)
		{
			new_index_data[i]=_index_data[i];
		}
		volatile int* old_index_data=_index_data;
		_index_data=new_index_data;
		delete[] old_index_data;

		// Reallocate index node pointers (again, without allowing the
		// existing node pointers to become invalid until they are no
		// longer needed).
		volatile node** new_index_nodes=
			new(std::nothrow) volatile node*[capacity];
		for (unsigned int i=0;i!=_size;++i)
		{
			new_index_nodes[i]=_index_nodes[i];
		}
		volatile node** old_index_nodes=_index_nodes;
		_index_nodes=new_index_nodes;
		delete[] old_index_nodes;

		// Create new nodes.
		while (_capacity<capacity)
		{
			node* n=new(std::nothrow) node;
			n->next=_free.next;
			_free.next=n;
			++_capacity;
		}
	}
}

pathname_table::node* pathname_table::insert(filer_window* w)
{
	// Ensure there is sufficient capacity for one more node.
	reserve(_size+1);

	// Parse pathname into filing system, special field and tail.
	char* pathtail=0;
	char* fs_special=0;
	fileswitch_fs_no fs_number=osfscontrol_set_temporary_fs(w->pathname(),
		&pathtail,&fs_special);

	// Calculate hash of pathname.
	unsigned int hash=pathname_hash(pathtail,strlen(pathtail),
		fs_number,fs_special);

	// Find point at which to insert.
	unsigned int f=pathname_find(this,hash);

	// Select node from free list.
	node* n=_free.next;
	_free.next=n->next;

	// Initialise node.
	n->next=0;
	n->refresh=0;
	n->window=w;
	n->index=f;

	// Increment _size, after first ensuring that this will not cause
	// the index to become unordered.
	_index_data[_size]=0xffffffff;
	_index_nodes[_size]=0;
	++_size;

	// Make space for new index entry.
	for (unsigned int i=_size-1;i!=f;--i)
	{
		_index_data[i]=_index_data[i-1];
		_index_nodes[i]=_index_nodes[i-1];
		_index_nodes[i]->index=i;
		_index_nodes[i-1]=0;
	}

	// Create new index entry.
	_index_data[f]=hash;
	_index_nodes[f]=n;

	// Return pointer to node.
	return n;
}

void pathname_table::erase(node* n)
{
	// Get location of node in index.
	unsigned int f=n->index;

	// Delete from index.
	// Each entry is modified as follows:
	// - first the link to the current node is broken
	// - next the hash is replaced with the new value
	// - finally the link to the new node is established
	// An index entry may therefore point to no node, or to the correct
	// node for the hash, but never to an incorrect node.
	_index_nodes[f]=0;
	for (unsigned int i=f+1;i!=_size;++i)
	{
		_index_data[i-1]=_index_data[i];
		_index_nodes[i-1]=_index_nodes[i];
		_index_nodes[i-1]->index=i-1;
		_index_nodes[i]=0;
	}

	// Remove last entry from index.
	// The last entry is now an inactive duplicate of the penultimate
	// entry, and can be removed by decrementing the size.
	--_size;

	// Return node to free list.
	n->next=_free.next;
	_free.next=n;
}

void pathname_table::refresh()
{
	if (_head.next)
	{
		// A chain has been started, so the UpCall handler will not now
		// alter _head until _tail is reset.

		// Make a pointer to the first node in the chain.
		node* p=_head.next;

		// Reset _head and _tail.  The UpCall handler will not now make
		// any changes to the chain about to be processed.
		_head.next=0;
		_tail=&_head;

		while (p)
		{
			// Keep a pointer to the current node, because the chain
			// pointer is about to move.
			node* n=p;

			// Move the chain pointer forward now, because the link is
			// about to be cleared.
			p=n->next;

			// Clear the forward link and the refresh pending flag
			// (in that order).  This is done before the filer window
			// is refreshed so that if there is further disc activity
			// it is refreshed again.
			n->next=0;
			n->refresh=0;

			// Refresh the filer window to which the node refers.
			n->window->refresh();
		}
	}
}