/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.IO; using System.Threading; using Ini; using JetBrains.Omea.Base; using JetBrains.Omea.Containers; using JetBrains.Omea.Database; using JetBrains.Omea.ResourceStore; using JetBrains.Omea.TextIndex; using Microsoft.Win32; namespace DBRepair { /** * OmniaMea database analysis and repair utility. */ class DBRepair { /// /// The main entry point for the application. /// [STAThread] static void Main( string[] args ) { args = args; try { new DBRepair().RunRepair(); } catch( Exception e ) { Console.WriteLine( "Exception processing database: " + e.ToString() ); } } private bool _fixErrors = false; private bool _textAnalyze = false; private bool _lowCheck = false; private bool _dump = false; private bool _dbdump = false; private bool _defrag = false; private bool _forceRebuild = false; private bool _calcSpace = false; private bool _backup = false; private bool _restore = false; private bool _deleteIndex = false; private bool _deleteTextIndex = false; private bool _ignoreMutex = false; private string _lastProgressLine = null; private IniFile _ini; private void ShowHelp() { Console.WriteLine( "Omea database diagnostics and repair utility" ); Console.WriteLine( "Usage: "); Console.WriteLine( " DBRepair.exe - diagnose errors" ); Console.WriteLine( " DBRepair.exe /fix - diagnose and fix errors" ); Console.WriteLine( " DBRepair.exe /lowcheck - diagnose low level records consistance for all tables" ); Console.WriteLine( " DBRepair.exe /rebuild - force the rebuild of database indexes" ); Console.WriteLine( " DBRepair.exe /defrag - defragment the database" ); Console.WriteLine( " DBRepair.exe /space - calculate wasted space in the database" ); Console.WriteLine( " DBRepair.exe /backup - backup the database" ); Console.WriteLine( " DBRepair.exe /restore - restore the database from the last backup" ); Console.WriteLine( " DBRepair.exe /dump - dump storage contents to the standard output" ); Console.WriteLine( " DBRepair.exe /dbdump - dump database contents for all tables to the standard output" ); Console.WriteLine( " DBRepair.exe /text - diagnose text index" ); Console.WriteLine( " DBRepair.exe /workdir - use a non-standard database directory" ); Console.WriteLine( " DBRepair.exe /deleteindex - delete database and text index files" ); Console.WriteLine( " DBRepair.exe /deletetextindex - delete text index files" ); } private void RunRepair() { string dbPath = null; string[] args = Environment.GetCommandLineArgs(); if ( args.Length > 1 ) { if ( args [1] == "-?" || args [1] == "/?" ) { ShowHelp(); return; } string arg = args [1].ToLower(); if ( ( arg == "-workdir" || arg == "/workdir" ) && args.Length > 2 ) { dbPath = args [2]; if ( args.Length > 3 ) { ProcessArgument( args [3] ); } } else { ProcessArgument( arg ); } } if ( dbPath == null ) { dbPath = RegUtil.DatabasePath; if ( dbPath == null ) { dbPath = Path.Combine( Environment.GetFolderPath( Environment.SpecialFolder.ApplicationData ), "JetBrains\\Omea" ); } } OMEnv.WorkDir = dbPath; MyPalStorage.DBPath = Path.Combine( OMEnv.WorkDir, "db" ); _ini = new IniFile(Path.Combine( OMEnv.WorkDir, "OmniaMea.ini" ) ); Console.WriteLine( "Processing database in " + dbPath ); bool omniaMeaIsNotRun; Mutex omniaMeaMux = new Mutex( true, "OmniaMeaMutex", out omniaMeaIsNotRun ); try { if ( !omniaMeaIsNotRun && !_ignoreMutex ) { Console.WriteLine( "Omea is currently running. Please close Omea before running DBRepair." ); return; } if ( _deleteIndex ) { DeleteIndex(); } else if ( _deleteTextIndex ) { DeleteFiles( OMEnv.WorkDir, "_*" ); } else if( _backup ) { string backupPath = _ini.ReadString( "ResourceStore", "BackupPath", string.Empty ); if( backupPath.Length > 0 ) { Console.Write( "Database backup in progress..." ); MyPalStorage.BackupDatabase( IOTools.Combine( backupPath, MyPalStorage._dbBackupFile ) ); Console.WriteLine( "\r \rDatabase backup done." ); } else { Console.WriteLine( "Backup path is not set. Run Omea, in Options | Paths set the path." ); } } else if( _restore ) { string backupPath = _ini.ReadString( "ResourceStore", "BackupPath", string.Empty ); if( backupPath.Length > 0 ) { Console.Write( "Restoring database from backup..." ); MyPalStorage.RestoreFromBackup( IOTools.Combine( backupPath, MyPalStorage._dbBackupFile ) ); Console.WriteLine( "\r \rDatabase restored from backup." ); } else { Console.WriteLine( "Backup path is not set. Run Omea, in Options | Paths set the path." ); } } else { if( !_textAnalyze || _lowCheck ) { ProcessDB(); } else { try { ProcessTextIndex( OMEnv.WorkDir + "\\_term.index" ); } catch( FormatException exc_ ) { Console.Error.WriteLine( exc_.Message ); } } } } finally { omniaMeaMux.Close(); } if ( !_dump && !_deleteIndex ) { Console.WriteLine( "Press Enter to continue..." ); Console.ReadLine(); } } private void ProcessArgument( string arg ) { if ( arg.StartsWith( "/" ) ) { arg = "-" + arg.Substring( 1 ); } if ( arg.ToLower() == "-fix" ) { _fixErrors = true; _forceRebuild = true; } else _fixErrors = false; _dump = ( arg == "-dump" ); _defrag = ( arg == "-defrag" ); _backup = ( arg == "-backup" ); _restore = ( arg == "-restore" ); if ( arg == "-dbdump" ) _dbdump = true; else _dbdump = false; switch( arg ) { case "-lowcheck": _lowCheck = true; break; case "-text": _textAnalyze = true; break; case "-rebuild": _forceRebuild = true; break; case "-space": _calcSpace = true; break; case "-deleteindex": _deleteIndex = true; break; case "-deletetextindex": _deleteTextIndex = true; break; case "-deleteindex-ignoremutex": _deleteIndex = true; _ignoreMutex = true; break; } } private void DeleteIndex() { DeleteFiles( MyPalStorage.DBPath, "*.blob" ); DeleteFiles( MyPalStorage.DBPath, "*.dbUtil" ); DeleteFiles( OMEnv.WorkDir, "_*" ); DeleteFiles( OMEnv.WorkDir, "liveforms.dat" ); if ( Directory.Exists( MyPalStorage.DBPath ) && Directory.GetFiles( MyPalStorage.DBPath ).Length == 0 ) { Directory.Delete( MyPalStorage.DBPath ); } RegUtil.DeleteValue( Registry.CurrentUser, @"Software\JetBrains\Omea", "DbPath" ); } private static void DeleteFiles( string dir, string mask ) { if ( !Directory.Exists( dir ) ) { return; } string[] dbFiles = Directory.GetFiles( dir, mask ); foreach (string dbFile in dbFiles) { string path = Path.Combine( dir, dbFile ); Console.WriteLine( "Deleting " + path ); File.Delete( path ); } } private void ProcessDB() { if ( !DBHelper.DatabaseExists( MyPalStorage.DBPath, "MyPal" ) ) { Console.WriteLine( "Omea database not found" ); return; } DBStructure dbStructure = new DBStructure( MyPalStorage.DBPath, "MyPal" ); dbStructure.ProgressListenerEvent += new DBStructure.ProgressEventHandler( dbStructure_ProgressListenerEvent ); Console.WriteLine( "Loading database..." ); bool structureCorrupted = false; try { dbStructure.LoadStructure( true ); } catch( Exception ) { structureCorrupted = true; } if ( structureCorrupted || !AllTablesExist( dbStructure ) ) { Console.WriteLine( "Rebuilding database structure..." ); dbStructure.Shutdown(); MyPalStorage.CreateDatabase(); dbStructure = new DBStructure( MyPalStorage.DBPath, "MyPal" ); dbStructure.LoadStructure( true ); _forceRebuild = true; } if ( _defrag ) { Console.WriteLine( "Defragmenting database..." ); dbStructure.Defragment(); } else if ( !dbStructure.IsDatabaseCorrect() ) { Console.WriteLine( "Database indexes are corrupt. Rebuilding..." ); dbStructure.RebuildIndexes(); } else if ( _forceRebuild ) { Console.WriteLine( "Performing forced rebuild of database indexes..." ); dbStructure.RebuildIndexes( true ); } else if ( _lowCheck ) { dbStructure.LowLevelCheck( ); return; } if ( _dbdump ) { Console.WriteLine( "Dumping database..." ); dbStructure.Dump(); } IDatabase db = dbStructure.OpenDatabase(); if ( _calcSpace ) { CalcSpace( db, "ResourceTypes" ); CalcSpace( db, "PropTypes" ); CalcSpace( db, "IntProps" ); CalcSpace( db, "StringProps" ); CalcSpace( db, "LongStringProps" ); CalcSpace( db, "StringListProps" ); CalcSpace( db, "DateProps" ); CalcSpace( db, "BlobProps" ); CalcSpace( db, "DoubleProps" ); CalcSpace( db, "Resources" ); CalcSpace( db, "Links" ); return; } ResourceStoreRepair rsRepair = new ResourceStoreRepair( db ); rsRepair.FixErrors = _fixErrors; rsRepair.DumpStructure = _dump; rsRepair.RepairProgress += new RepairProgressEventHandler( RsRepair_OnRepairProgress ); rsRepair.Run(); } private bool AllTablesExist( DBStructure structure ) { foreach( string tableName in new string[] { "PropTypes", "ResourceTypes", "IntProps", "StringProps", "LongStringProps", "StringListProps", "DateProps", "BlobProps", "DoubleProps", "Resources", "Links" } ) { try { structure.GetTable( tableName ); } catch( TableDoesNotExistException ) { return false; } } return true; } private void RsRepair_OnRepairProgress( object sender, RepairProgressEventArgs e ) { Console.WriteLine( e.Message ); } private void CalcSpace( IDatabase db, string tableName ) { ITable tbl = db.GetTable( tableName ); RecordsCounts counts = tbl.ComputeWastedSpace(); if ( counts.NormalRecordCount == 0 && counts.TotalRecordCount == 0 ) { Console.WriteLine( "{0}: empty", tableName ); } else { long deletedRecordsCount = counts.TotalRecordCount - counts.NormalRecordCount; Console.WriteLine( "{0}: total {1} records , wasted {2} records, percentage {3}%", tableName, counts.TotalRecordCount, deletedRecordsCount, (deletedRecordsCount * 100) / counts.TotalRecordCount ); } } //------------------------------------------------------------------------- private void ProcessTextIndex( string TermIndexName ) { TermIndexAccessor termIndex = new TermIndexAccessor( TermIndexName ); try { Console.Write( "ok\nChecking TermIndex component..." ); termIndex.Load(); Console.Write( "ok\nPerforming cross component linkage checks..." ); CrossIndexChecks( termIndex ); Console.WriteLine( "ok\nText index corruption check passed:" ); Console.WriteLine( "\t" + termIndex.TermsNumber + " term entries parsed" ); } finally { termIndex.Close(); } } protected void CrossIndexChecks( TermIndexAccessor termIndex ) { foreach( KeyPair pair in termIndex.Keys ) { TermIndexRecord termRecord = termIndex.GetRecordByHandle( pair._offset ); for( int j = 0; j < termRecord.DocsNumber; j++ ) { Entry entry = termRecord.GetEntryAt( j ); if( entry.DocIndex < -1 ) throw new FormatException( "DocIndex is negative in the TermIndex record entry" ); if( entry.Count <= 0 ) throw new FormatException( "Number of term instances is negative in the TermIndex record" ); } } } private void dbStructure_ProgressListenerEvent( string progress, int tableNum, int tableCount ) { if ( progress != _lastProgressLine ) { _lastProgressLine = progress; Console.WriteLine( progress ); } } } }