/// <summary> /// Try enter in shared lock (read) - Call action if request a new lock /// [Non ThreadSafe] /// </summary> private Action LockShared() { lock (_disk) { if (_state != LockState.Unlocked) { return () => { } } ; _disk.Lock(LockState.Shared, _timeout); _state = LockState.Shared; _shared = true; _log.Write(LoggerWrap.LOCK, "entered in shared lock mode"); this.AvoidDirtyRead(); return(() => { _shared = false; _disk.Unlock(LockState.Shared); _state = LockState.Unlocked; _log.Write(LoggerWrap.LOCK, "exited shared lock mode"); }); } }
public void Initialize(LoggerWrap log, string password) { // get log instance to disk _log = log; // if is read only, journal must be disabled if (_options.FileMode == FileMode.ReadOnly) { _options.Journal = false; } // open/create file using read only/exclusive options _stream = CreateFileStream(_filename, _options.FileMode == FileMode.ReadOnly ? System.IO.FileMode.Open : System.IO.FileMode.OpenOrCreate, _options.FileMode == FileMode.ReadOnly ? FileAccess.Read : FileAccess.ReadWrite, _options.FileMode == FileMode.Exclusive ? FileShare.None : FileShare.ReadWrite); // if file is new, initialize if (_stream.Length == 0) { _log.Write(LoggerWrap.DISK, "initialize new datafile"); // set datafile initial size _stream.SetLength(_options.InitialSize); // create datafile LiteEngine.CreateDatabase(_stream, password); } }
/// <summary> /// Read page bytes from disk /// </summary> public virtual byte[] ReadPage(uint pageID) { var buffer = new byte[BasePage.PAGE_SIZE]; var position = BasePage.GetSizeOfPages(pageID); // position cursor if (_stream.Position != position) { _stream.Seek(position, SeekOrigin.Begin); } // read bytes from data file _stream.Read(buffer, 0, BasePage.PAGE_SIZE); _log.Write(LoggerWrap.DISK, "read page #{0:0000} :: {1}", pageID, (PageType)buffer[PAGE_TYPE_POSITION]); return(buffer); }
/// <summary> /// Try recovery journal file (if exists). Restore original datafile /// Journal file are NOT encrypted (even when datafile are encrypted) /// </summary> public void Recovery() { var fileSize = _disk.FileLength; var pages = 0; // read all journal pages foreach (var buffer in _disk.ReadJournal()) { // read pageID (first 4 bytes) var pageID = BitConverter.ToUInt32(buffer, 0); _log.Write(LoggerWrap.RECOVERY, "recover page #{0:0000}", pageID); // if header, read all byte (to get original filesize) if (pageID == 0) { var header = (HeaderPage)BasePage.ReadPage(buffer); fileSize = BasePage.GetSizeOfPages(header.LastPageID + 1); } // write in stream (encrypt if datafile is encrypted) _disk.WritePage(pageID, _crypto == null || pageID == 0 ? buffer : _crypto.Encrypt(buffer)); pages++; } // no pages, no recovery if (pages == 0) { return; } _log.Write(LoggerWrap.RECOVERY, "resize datafile to {0} bytes", fileSize); // redim filesize if grow more than original before rollback _disk.SetLength(fileSize); // empty journal file _disk.ClearJournal(); }
public void Initialize(LoggerWrap log, string password) { // get log instance to disk _log = log; // if stream are empty, create header page and save to stream if (_stream.Length == 0) { _log.Write(LoggerWrap.DISK, "initialize new datafile"); // create datafile LiteEngine.CreateDatabase(_stream, password); } }
/// <summary> /// Get the collection page only when needed. Gets from pager always to grantee that wil be the last (in case of clear cache will get a new one - pageID never changes) /// </summary> private CollectionPage GetCollectionPage(string name, bool addIfNotExits) { if (name == null) { return(null); } // search my page on collection service var col = _collections.Get(name); if (col == null && addIfNotExits) { _log.Write(LoggerWrap.COMMAND, "create new collection '{0}'", name); col = _collections.Add(name); } return(col); }
/// <summary> /// Add a new collection. Check if name the not exists /// </summary> public CollectionPage Add(string name) { if (string.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); } if (!CollectionPage.NamePattern.IsMatch(name)) { throw LiteException.InvalidFormat("Collection", name); } _log.Write(LoggerWrap.COMMAND, "creating new collection '{0}'", name); // get header marked as dirty because I will use header after (and NewPage can get another header instance) var header = _pager.GetPage <HeaderPage>(0); // check limit count (8 bytes per collection = 4 to string length, 4 for uint pageID) if (header.CollectionPages.Sum(x => x.Key.Length + 8) + name.Length + 8 >= CollectionPage.MAX_COLLECTIONS_SIZE) { throw LiteException.CollectionLimitExceeded(CollectionPage.MAX_COLLECTIONS_SIZE); } // get new collection page (marked as dirty) var col = _pager.NewPage <CollectionPage>(); // add this page to header page collection header.CollectionPages.Add(name, col.PageID); col.CollectionName = name; // set header page as dirty _pager.SetDirty(header); // create PK index var pk = _indexer.CreateIndex(col); pk.Field = "_id"; pk.Unique = true; return(col); }
public virtual void Dispose() { if (_journal != null) { _log.Write(LoggerWrap.DISK, "close journal file '{0}'", Path.GetFileName(_journalFilename)); _journal.Dispose(); _journal = null; FileHelper.TryDelete(_journalFilename); } if (_stream != null) { _log.Write(LoggerWrap.DISK, "close datafile '{0}'", Path.GetFileName(_filename)); _stream.Dispose(); _stream = null; } }
/// <summary> /// Discard only dirty pages /// [ThreadSafe] /// </summary> public void DiscardDirtyPages() { _log.Write(LoggerWrap.CACHE, "clearing dirty pages from cache"); _dirty.Clear(); }