public byte[] ReadBlob(long pageIndex)
        {
            //Create buffer from the length specified in the first page
            byte[] buffer = new byte[pages[(int)pageIndex].total_blob_length];

            //Begin reading pages
            long nextPageIndex    = pageIndex;
            int  currentPageIndex = 0; //Read index for writing to our buffer

            //Loop through this page and it's decendants
            while (nextPageIndex != -1)
            {
                //Get page
                DatabasePage page = pages[(int)nextPageIndex];

                //Get index of the next page
                nextPageIndex = page.next_page_index;

                //Calculate read parameters
                int readOffset = currentPageIndex * pageContentSize;
                int readLength = Math.Min(pageContentSize, buffer.Length - readOffset);

                //Jump to and read
                file.Position = GetPageFileOffset(page.index) + PAGE_HEADER_SIZE;
                file.Read(buffer, readOffset, readLength);
                currentPageIndex++;
            }

            return(buffer);
        }
        private DatabasePage CreateDatabasePage()
        {
            //Jump to new page location
            file.Position = GetPageFileOffset(pages.Count);

            //Write placeholder data
            for (int i = 0; i < pageTotalSize; i++)
            {
                file.WriteByte(0x00);
            }

            //Add page info
            DatabasePage pageInfo = new DatabasePage
            {
                index           = pages.Count,
                type            = PAGE_TYPE_ORPHAN,
                modified_utc    = GetCurrentUtcEpoch(),
                next_page_index = -1
            };

            pages.Add(pageInfo);
            orphanedPages.Add(pageInfo);

            //Jump to counter and update
            file.Position = 16;
            WriteInt64ToStream(pages.Count);

            //Jump to new page location
            file.Position = GetPageFileOffset(pageInfo.index);

            return(pageInfo);
        }
        public void ReadHeader()
        {
            //Jump to beginning
            file.Position = 0;

            //Read file signature
            if (ReadInt32FromStream() != FILE_SIN)
            {
                throw new Exception("This is not a Delta Web Map Content file.");
            }

            //Read version
            if (ReadInt32FromStream() != 1)
            {
                throw new Exception("Unknown file version.");
            }

            //Read header data
            pageContentSize = ReadInt32FromStream();
            modifiedUtc     = ReadInt32FromStream();
            long pageCount = ReadInt64FromStream();

            //Read data for each page
            pages = new List <DatabasePage>();
            for (long i = 0; i < pageCount; i += 1)
            {
                //Jump to page
                file.Position = GetPageFileOffset(i);

                //Read
                int  pageType        = ReadInt32FromStream();
                int  pageModifiedUtc = ReadInt32FromStream();
                int  pageContentSize = ReadInt32FromStream();
                int  pageReserved    = ReadInt32FromStream();
                long pageNextIndex   = ReadInt64FromStream();

                //Make
                var page = new DatabasePage
                {
                    index             = i,
                    type              = pageType,
                    modified_utc      = pageModifiedUtc,
                    total_blob_length = pageContentSize,
                    flags             = pageReserved,
                    next_page_index   = pageNextIndex
                };

                //Add
                pages.Add(page);

                //If this is an orphaned page, set it as so
                if (pageTotalSize == PAGE_TYPE_ORPHAN)
                {
                    orphanedPages.Add(page);
                }
            }
        }
        public long WriteNewBlob(int objectType, byte[] payload)
        {
            //Calculate number of chunks we need
            int objectPageCount = (payload.Length / pageContentSize) + 1;

            //Find orphaned pages. These are pages that are unused
            DatabasePage[] objectPages     = new DatabasePage[objectPageCount];
            int            objectPageIndex = 0;

            foreach (var p in orphanedPages)
            {
                if (p.type == PAGE_TYPE_ORPHAN)
                {
                    objectPages[objectPageIndex] = p;
                    objectPageIndex++;
                    if (objectPageIndex == objectPageCount)
                    {
                        break;
                    }
                }
            }

            //If we need additional pages, create them
            while (objectPageIndex < objectPageCount)
            {
                objectPages[objectPageIndex] = CreateDatabasePage();
                objectPageIndex++;
            }

            //Now write content to each page
            for (int i = 0; i < objectPageCount; i += 1)
            {
                //Find the index of the next page
                long nextPageIndex;
                if (i + 1 == objectPageCount)
                {
                    nextPageIndex = -1;
                }
                else
                {
                    nextPageIndex = objectPages[i + 1].index;
                }

                //Make flags
                int flags = 0;
                if (i != 0) //If this is not the first one, set the continuation flag to true
                {
                    flags |= 1 << DatabasePage.FLAG_CONTINUATION;
                }

                //Write content
                UpdateDatabasePage(objectPages[i], objectType, nextPageIndex, payload, i, flags);
            }

            return(objectPages[0].index);
        }
        private void UpdateDatabasePage(DatabasePage page, int pageType, long nextPageIndex, byte[] buffer, int pageIndex, int flags)
        {
            //Update metadata
            UpdateDatabasePage(page, pageType, nextPageIndex, buffer.Length, flags);

            //Calculate where to read from the buffer
            int bufferReadOffset = pageIndex * pageContentSize;
            int bufferReadLength = Math.Min(pageContentSize, buffer.Length - bufferReadOffset);

            //Copy content
            file.Write(buffer, bufferReadOffset, bufferReadLength);
        }
        public void DeleteBlob(long pageIndex)
        {
            //Get first page
            long nextPageIndex = pageIndex;

            //Loop through this page and it's decendants
            while (nextPageIndex != -1)
            {
                //Get page
                DatabasePage page = pages[(int)nextPageIndex];

                //Get index of the next page before it's removed
                nextPageIndex = page.next_page_index;

                //Update the page selected
                UpdateDatabasePage(page, PAGE_TYPE_ORPHAN, -1, 0, 0x00);
            }
        }
        private void UpdateDatabasePage(DatabasePage page, int pageType, long nextPageIndex, int totalBlobLength, int flags)
        {
            //Get current last modified time
            int modifiedUtc = GetCurrentUtcEpoch();

            //Jump to file header to write the current UTC time
            file.Position = 12;
            WriteInt32ToStream(modifiedUtc);

            //Jump to the header for this page
            file.Position = GetPageFileOffset(page.index);

            //Update type
            WriteInt32ToStream(pageType);
            page.type = pageType;

            //Update UTC time
            WriteInt32ToStream(modifiedUtc);
            page.modified_utc = modifiedUtc;

            //Update blob length
            WriteInt32ToStream(totalBlobLength);
            page.total_blob_length = totalBlobLength;

            //Update flags
            WriteInt32ToStream(flags);
            page.flags = flags;

            //Update next page index
            WriteInt64ToStream(nextPageIndex);
            page.next_page_index = nextPageIndex;

            //Add/remove to orphaned pages if needed
            bool isInOrphaned = orphanedPages.Contains(page);

            if (pageType == PAGE_TYPE_ORPHAN && !isInOrphaned)
            {
                orphanedPages.Add(page);
            }
            if (pageTotalSize != PAGE_TYPE_ORPHAN && isInOrphaned)
            {
                orphanedPages.Remove(page);
            }
        }