| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 | /* * EliasDB * * Copyright 2016 Matthias Ladkau. All rights reserved. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */package pagingimport (	"errors"	"devt.de/krotik/eliasdb/storage/file"	"devt.de/krotik/eliasdb/storage/paging/view")/*Common paged storage file related errors*/var (	ErrFreePage = errors.New("Cannot allocate/free a free page")	ErrHeader   = errors.New("Cannot modify header record"))/*PagedStorageFile data structure*/type PagedStorageFile struct {	storagefile *file.StorageFile       // StorageFile which is wrapped	header      *PagedStorageFileHeader // Header object}/*NewPagedStorageFile wraps a given StorageFile and returns a PagedStorageFile.*/func NewPagedStorageFile(storagefile *file.StorageFile) (*PagedStorageFile, error) {	var header *PagedStorageFileHeader	record, err := storagefile.Get(0)	if err != nil {		return nil, err	}	// Check if this is a new header record or not	isnew := record.ReadInt16(0) == 0	header = NewPagedStorageFileHeader(record, isnew)	return &PagedStorageFile{storagefile, header}, nil}/*StorageFile returns the wrapped StorageFile.*/func (psf *PagedStorageFile) StorageFile() *file.StorageFile {	return psf.storagefile}/*Header returns the header object of this PagedStorageFile.*/func (psf *PagedStorageFile) Header() *PagedStorageFileHeader {	return psf.header}/*AllocatePage allocates a new page of a specific type.*/func (psf *PagedStorageFile) AllocatePage(pagetype int16) (uint64, error) {	var record *file.Record	var err error	if pagetype == view.TypeFreePage {		return 0, ErrFreePage	}	// Check first the free list	ptr := psf.header.FirstListElement(view.TypeFreePage)	isnew := ptr == 0	if !isnew {		// If there is something on the free list set the pointer		// for the first item to the second item. The first item		// becomes our newly allocated element.		nextptr, err := psf.Next(ptr)		if err != nil {			return 0, err		}		// Get the record - error checking already done in the		// previous psf.Next call		record, _ = psf.storagefile.Get(ptr)		psf.header.SetFirstListElement(view.TypeFreePage, nextptr)	} else {		// Need to create a new rcord		ptr = psf.header.LastListElement(view.TypeFreePage)		if ptr == 0 {			// If the file is new the first pointer is 1			ptr = 1		}		// Get the record - if it fails we need to return before		// increasing the last list element pointer		record, err = psf.storagefile.Get(ptr)		if err != nil {			return 0, err		}		// The last list element pointer is used to point to the next free record		// it is not actuallz the last element of the list.		psf.header.SetLastListElement(view.TypeFreePage, ptr+1)	}	// Set the view data on the record	var pageview *view.PageView	// Add a temp. page view so we can modify the record	if isnew {		pageview = view.NewPageView(record, pagetype)	} else {		pageview = view.GetPageView(record)	}	oldtail := psf.header.LastListElement(pagetype)	record.ClearData()	pageview.SetType(pagetype)	pageview.SetPrevPage(oldtail)	pageview.SetNextPage(0)	// Check if this page was the first of its type	if oldtail == 0 {		psf.header.SetFirstListElement(pagetype, ptr)	}	// New allocated record is now the new last element	psf.header.SetLastListElement(pagetype, ptr)	// We can release the record now	psf.storagefile.ReleaseInUse(record)	// Need to fix up the pointer of the former previous element	if oldtail != 0 {		record, err = psf.storagefile.Get(oldtail)		if err != nil {			return 0, err		}		pageview = view.GetPageView(record)		pageview.SetNextPage(ptr)		psf.storagefile.ReleaseInUse(record)	}	// Remove temp. page view	record.SetPageView(nil)	return ptr, nil}/*FreePage frees a given page and adds it to the free list.*/func (psf *PagedStorageFile) FreePage(id uint64) error {	if id == 0 {		return ErrHeader	}	record, err := psf.storagefile.Get(id)	if err != nil {		return err	}	pageview := view.GetPageView(record)	pagetype := pageview.Type()	if pagetype == view.TypeFreePage {		psf.storagefile.ReleaseInUse(record)		return ErrFreePage	}	prev := pageview.PrevPage()	next := pageview.NextPage()	// Put the page to the front of the free list	pageview.SetType(view.TypeFreePage)	pageview.SetNextPage(psf.header.FirstListElement(view.TypeFreePage))	pageview.SetPrevPage(0)	psf.header.SetFirstListElement(view.TypeFreePage, id)	// NOTE The prev pointers will always point to 0 for records in the	// free list. There is no need to update them.	psf.storagefile.ReleaseInUse(record)	// Remove page from its old list - an error in the below leaves	// the lists in an inconsistent state.	if prev != 0 {		record, err = psf.storagefile.Get(prev)		if err != nil {			return err		}		pageview := view.GetPageView(record)		pageview.SetNextPage(next)		psf.storagefile.ReleaseInUse(record)	} else {		psf.header.SetFirstListElement(pagetype, next)	}	if next != 0 {		record, err = psf.storagefile.Get(next)		if err != nil {			return err		}		pageview := view.GetPageView(record)		pageview.SetPrevPage(prev)		psf.storagefile.ReleaseInUse(record)	} else {		psf.header.SetLastListElement(pagetype, prev)	}	return nil}/*First returns the first page of a list of a given type.*/func (psf *PagedStorageFile) First(pagetype int16) uint64 {	return psf.header.FirstListElement(pagetype)}/*Last returns the first page of a list of a given type.*/func (psf *PagedStorageFile) Last(pagetype int16) uint64 {	return psf.header.LastListElement(pagetype)}/*Next returns the next page of a given page in a list.*/func (psf *PagedStorageFile) Next(id uint64) (uint64, error) {	record, err := psf.storagefile.Get(id)	if err != nil {		return 0, err	}	defer psf.storagefile.ReleaseInUse(record)	pageview := view.GetPageView(record)	return pageview.NextPage(), nil}/*Prev returns the previous page of a given page in a list.*/func (psf *PagedStorageFile) Prev(id uint64) (uint64, error) {	record, err := psf.storagefile.Get(id)	if err != nil {		return 0, err	}	defer psf.storagefile.ReleaseInUse(record)	pageview := view.GetPageView(record)	return pageview.PrevPage(), nil}/*Flush writes all pending data to disk.*/func (psf *PagedStorageFile) Flush() error {	psf.storagefile.ReleaseInUse(psf.header.record)	if err := psf.storagefile.Flush(); err != nil {		// If an error happens try to recover by putting		// the header record back in use		psf.storagefile.Get(0)		return err	}	// No particular error checking for Get operation as	// it should succeed if the previous Flush was successful.	record, _ := psf.storagefile.Get(0)	psf.header = NewPagedStorageFileHeader(record, false)	return nil}/*Rollback discards all changes which were done after the last flush.The PageStorageFile object should be discarded if somethinggoes wrong during a rollback operation.*/func (psf *PagedStorageFile) Rollback() error {	psf.storagefile.Discard(psf.header.record)	if err := psf.storagefile.Rollback(); err != nil {		// If there is a problem try to get the header record back		// otherwise close operations may fail later		psf.header.record, _ = psf.storagefile.Get(0)		return err	}	// No particular error checking for Get operation as	// it should succeed if the previous Rollback was successful.	record, _ := psf.storagefile.Get(0)	psf.header = NewPagedStorageFileHeader(record, record.ReadInt16(0) == 0)	return nil}/*Close commits all data and closes all physical files.*/func (psf *PagedStorageFile) Close() error {	if psf.header != nil {		psf.storagefile.ReleaseInUse(psf.header.record)		psf.header = nil	}	if err := psf.storagefile.Close(); err != nil {		return err	}	psf.storagefile = nil	return nil}
 |