Example #1
0
        public Library(WebLibraryDetail web_library_detail)
        {
            this.web_library_detail = web_library_detail;

            Logging.Info("Library basepath is at {0}", LIBRARY_BASE_PATH);
            Logging.Info("Library document basepath is at {0}", LIBRARY_DOCUMENTS_BASE_PATH);

            Directory.CreateDirectory(LIBRARY_BASE_PATH);
            Directory.CreateDirectory(LIBRARY_DOCUMENTS_BASE_PATH);

            library_db              = new LibraryDB(LIBRARY_BASE_PATH);
            folder_watcher_manager  = new FolderWatcherManager(this);
            library_index           = new LibraryIndex(this);
            ai_tag_manager          = new AITagManager(this);
            recently_read_manager   = new RecentlyReadManager(this);
            blackwhite_list_manager = new BlackWhiteListManager(this);
            password_manager        = new PasswordManager(this);
            expedition_manager      = new ExpeditionManager(this);

            // Start loading the documents in the background...
            SafeThreadPool.QueueUserWorkItem(o => BuildFromDocumentRepository());
        }
Example #2
0
        public List <LibraryItem> GetLibraryItems(string extension, List <string> fingerprints = null, int MaxRecordCount = 0)
        {
            List <LibraryItem> results             = new List <LibraryItem>();
            List <Exception>   database_corruption = new List <Exception>();

            try
            {
                lock (DBAccessLock.db_access_lock)
                {
                    using (var connection = GetConnection())
                    {
                        connection.Open();

                        string command_string = "SELECT fingerprint, extension, md5, data FROM LibraryItem WHERE 1=1 ";
                        command_string += turnArgumentSetIntoQueryPart("fingerprint", fingerprints);
                        command_string += turnArgumentIntoQueryPart("extension", extension);
                        if (MaxRecordCount > 0)
                        {
                            // http://www.sqlitetutorial.net/sqlite-limit/
                            command_string += " LIMIT @maxnum";
                        }

                        using (var command = new SQLiteCommand(command_string, connection))
                        {
                            //turnArgumentIntoQueryParameter(command, "fingerprint", fingerprint);
                            turnArgumentIntoQueryParameter(command, "extension", extension);
                            if (MaxRecordCount > 0)
                            {
                                command.Parameters.AddWithValue("@maxnum", MaxRecordCount);
                            }

                            using (SQLiteDataReader reader = command.ExecuteReader())
                            {
                                while (reader.Read())
                                {
                                    LibraryItem result = new LibraryItem();

                                    int field_count = 0;

                                    // Read the record in 2-3 gangs, as there's some DBs out there which have the BLOB as NOT A BLOB but as a STRING type instead:
                                    // this is probably caused by manual editing (using SQLite CLI or other means) of the BLOB record.

                                    // gang 1: load the field count and header fields: these almost never fail.
                                    try
                                    {
                                        field_count = reader.FieldCount;

                                        result.fingerprint = reader.GetString(0);
                                        result.extension   = reader.GetString(1);
                                        result.md5         = reader.GetString(2);
                                    }
                                    catch (Exception ex)
                                    {
                                        string msg = String.Format("LibraryDB::GetLibraryItems: Database record #{4} gang 1 decode failure for DB '{0}': fingerprint={1}, ext={2}, md5={3}, field_count={5}.",
                                                                   library_path,
                                                                   String.IsNullOrEmpty(result.fingerprint) ? "???" : result.fingerprint,
                                                                   String.IsNullOrEmpty(result.extension) ? "???" : result.extension,
                                                                   String.IsNullOrEmpty(result.md5) ? "???" : result.md5,
                                                                   reader.StepCount, // ~= results.Count + database_corruption.Count
                                                                   field_count
                                                                   );
                                        Logging.Error(ex, "{0}", msg);

                                        Exception ex2 = new Exception(msg, ex);

                                        database_corruption.Add(ex2);

                                        // it's no use to try to decode the rest of the DB record: it is lost to us
                                        continue;
                                    }

                                    if (true)
                                    {
                                        Exception ex2 = null;

                                        long total_bytes = 0;

                                        // gang 2: get the BLOB
                                        try
                                        {
                                            total_bytes = reader.GetBytes(3, 0, null, 0, 0);
                                            result.data = new byte[total_bytes];
                                            long total_bytes2 = reader.GetBytes(3, 0, result.data, 0, (int)total_bytes);
                                            if (total_bytes != total_bytes2)
                                            {
                                                throw new Exception("Error reading blob - blob size different on each occasion.");
                                            }

                                            results.Add(result);
                                            continue;
                                        }
                                        catch (Exception ex)
                                        {
                                            string msg = String.Format("LibraryDB::GetLibraryItems: Database record #{4} BLOB decode failure for DB '{0}': fingerprint={1}, ext={2}, md5={3}, BLOB length={5}.",
                                                                       library_path,
                                                                       String.IsNullOrEmpty(result.fingerprint) ? "???" : result.fingerprint,
                                                                       String.IsNullOrEmpty(result.extension) ? "???" : result.extension,
                                                                       String.IsNullOrEmpty(result.md5) ? "???" : result.md5,
                                                                       reader.StepCount, // ~= results.Count + database_corruption.Count
                                                                       total_bytes
                                                                       );

                                            ex2 = new Exception(msg, ex);

                                            // gang 3: get at the BLOB-née-STRING in an indirect way
                                            object[] fields = new object[5];

                                            try
                                            {
                                                reader.GetValues(fields);
                                                byte[] arr = fields[3] as byte[];
                                                if (arr != null)
                                                {
                                                    string blob = Encoding.UTF8.GetString(arr, 0, arr.Length);

                                                    result.data = new byte[arr.Length];
                                                    Array.Copy(arr, result.data, arr.Length);

                                                    results.Add(result);

                                                    Logging.Warn("LibraryDB::GetLibraryItems: Database record #{0} BLOB field is instead decoded as UTF8 STRING, following this RESOLVED ERROR: {1}\n  Decoded STRING content:\n{2}",
                                                                 reader.StepCount, // ~= results.Count + database_corruption.Count
                                                                 ex2.ToStringAllExceptionDetails(),
                                                                 blob);

                                                    continue;
                                                }
                                                else
                                                {
                                                    throw new Exception("Cannot extract BLOB field.");
                                                }
                                            }
                                            catch (Exception ex3)
                                            {
                                                Logging.Error(ex2);
                                                Logging.Error(ex3);

                                                database_corruption.Add(ex2);
                                            }
                                        }
                                    }
                                }

                                reader.Close();
                            }
                        }

                        connection.Close();
                    }

                    //
                    // see SO link above at the `DBAccessLock.db_access_lock` declaration.
                    //
                    // We keep this *inside* the critical section so that we know we'll be the only active SQLite
                    // action which just transpired.
                    // *This* is also the reason why I went with a *global* lock (singleton) for *all* databases,
                    // even while *theoretically* this is *wrong* or rather: *unnecessary* as the databases
                    // i.e. Qiqqa Libraries shouldn't bite one another. I, however, need to ensure that the
                    // added `System.Data.SQLite.SQLiteConnection.ClearAllPools();` statements don't foul up
                    // matters in library B while lib A I/O is getting cleaned up.
                    //
                    // In short: Yuck. + Cave canem.
                    //
                    SQLiteConnection.ClearAllPools();
                }
            }
            catch (Exception ex)
            {
                Logging.Error(ex, "LibraryDB::GetLibraryItems: Database I/O failure for DB '{0}'.", library_path);
                LibraryDB.FurtherDiagnoseDBProblem(ex, database_corruption, library_path);
                throw;
            }

            if (database_corruption.Count > 0)
            {
                // report database corruption: the user may want to recover from this ASAP!
                if (MessageBoxes.AskErrorQuestion(true, "Library '{0}' has some data corruption. Do you want to abort the application to attempt recovery using external tools, e.g. a data restore from backup?\n\nWhen you answer NO, we will continue with what we could recover so far instead.\n\n\nConsult the Qiqqa logfiles to see the individual corruptions reported.",
                                                  library_path))
                {
                    Logging.Warn("User chose to abort the application on database corruption report");
                    Environment.Exit(3);
                }
            }

            return(results);
        }
Example #3
0
        public void PutBlob(string fingerprint, string extension, byte[] data)
        {
            // Guard
            if (String.IsNullOrWhiteSpace(fingerprint))
            {
                throw new Exception("Can't store in LibraryDB with null fingerprint.");
            }
            if (String.IsNullOrWhiteSpace(extension))
            {
                throw new Exception("Can't store in LibraryDB with null extension.");
            }

            // Calculate the MD5 of this blobbiiiieeeeee
            string md5 = StreamMD5.FromBytes(data);

            try
            {
                lock (DBAccessLock.db_access_lock)
                {
                    using (var connection = GetConnection())
                    {
                        connection.Open();
                        using (var transaction = connection.BeginTransaction())
                        {
                            bool managed_update = false;

                            using (var command = new SQLiteCommand("UPDATE LibraryItem SET MD5=@md5, DATA=@data WHERE fingerprint=@fingerprint AND extension=@extension", connection, transaction))
                            {
                                command.Parameters.AddWithValue("@md5", md5);
                                command.Parameters.AddWithValue("@data", data);
                                command.Parameters.AddWithValue("@fingerprint", fingerprint);
                                command.Parameters.AddWithValue("@extension", extension);
                                int num_rows_updated = command.ExecuteNonQuery();
                                if (1 == num_rows_updated)
                                {
                                    managed_update = true;
                                }
                            }

                            if (!managed_update)
                            {
                                using (var command = new SQLiteCommand("INSERT INTO LibraryItem(fingerprint, extension, md5, data) VALUES(@fingerprint, @extension, @md5, @data)", connection, transaction))
                                {
                                    command.Parameters.AddWithValue("@fingerprint", fingerprint);
                                    command.Parameters.AddWithValue("@extension", extension);
                                    command.Parameters.AddWithValue("@md5", md5);
                                    command.Parameters.AddWithValue("@data", data);
                                    command.ExecuteNonQuery();
                                }
                            }

                            transaction.Commit();
                        }
                        connection.Close();
                    }

                    //
                    // see SO link above at the `DBAccessLock.db_access_lock` declaration.
                    //
                    // We keep this *inside* the critical section so that we know we'll be the only active SQLite
                    // action which just transpired.
                    // *This* is also the reason why I went with a *global* lock (singleton) for *all* databases,
                    // even while *theoretically* this is *wrong* or rather: *unnecessary* as the databases
                    // i.e. Qiqqa Libraries shouldn't bite one another. I, however, need to ensure that the
                    // added `System.Data.SQLite.SQLiteConnection.ClearAllPools();` statements don't foul up
                    // matters in library B while lib A I/O is getting cleaned up.
                    //
                    // In short: Yuck. + Cave canem.
                    //
                    SQLiteConnection.ClearAllPools();
                }
            }
            catch (Exception ex)
            {
                Logging.Error(ex, "LibraryDB::PutBLOB: Database I/O failure for DB '{0}'.", library_path);
                LibraryDB.FurtherDiagnoseDBProblem(ex, null, library_path);
                throw;
            }
        }
Example #4
0
        protected virtual void Dispose(bool disposing)
        {
            Logging.Debug("Library::Dispose({0}) @{1}", disposing, dispose_count);

            try
            {
                LibraryIsKilled = true;

                if (dispose_count == 0)
                {
                    // Get rid of managed resources / get rid of cyclic references:

                    // Do we need to check that the library has finished being loaded?

                    // Switch off the living things
                    library_index?.Dispose();
                    folder_watcher_manager?.Dispose();

                    // NULL the memory database
                    Utilities.LockPerfTimer l2_clk = Utilities.LockPerfChecker.Start();
                    lock (pdf_documents_lock)
                    {
                        l2_clk.LockPerfTimerStop();
                        pdf_documents.Clear();
                        pdf_documents = null;
                    }
                }

                // Clear the references for sanity's sake
                expedition_manager      = null;
                password_manager        = null;
                blackwhite_list_manager = null;
                recently_read_manager   = null;
                ai_tag_manager          = null;
                library_index           = null;
                folder_watcher_manager  = null;
                library_db = null;

#if false
                web_library_detail = null;       // cyclic reference as WebLibraryDetail instance reference us, so we MUST nil this one to break the cycle for the GC to work well.
#else
                // cyclic reference as WebLibraryDetail instance reference us, so we MUST nil this one to break the cycle for the GC to work well.
                //
                // WARNING:
                // The most obvious way (see above in `#if false` branch) would be to NULL the weblibdetail reference, but this will cause all sorts of extremely
                // nasty crashes, including memory corruption, as this reference is accessed in Library background task(s) which might discover that the
                // library at hand has been killed rather late.
                //
                // When those code chunks, e.g. *anything* inside `BuildFromDocumentRepository()`, crash on a NULL dereference of any of the other NULLed
                // library members, the error resolution code highly depends on a *still working* web_library_detail reference/instance.
                // To resolve the cyclic reference in there (as the web_lib_detail has a `Library` reference), we hack this by creating a *temporary*
                // intermediate web_library_detail instance, which is a copy of the original *sans Library reference*.
                // We DO NOT nuke the Library member in the original web_library_detail as that would cause all sorts of other harm since there's other
                // code which depends on a certain valid lifetime of that instance and that code should dispose of the record once it is done using it...
                //
                // Cloning...
                web_library_detail = web_library_detail.CloneSansLibraryReference();
#endif
            }
            catch (Exception ex)
            {
                Logging.Error(ex);
            }

            ++dispose_count;
        }