| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | |
| 3 | using System; |
| 4 | using System.IO; |
| 5 | using System.Collections; |
| 6 | using System.Collections.Generic; |
| 7 | using WixToolset.Dtf.WindowsInstaller; |
| 8 | using WixToolset.Dtf.WindowsInstaller.Package; |
| 9 | |
| 10 | namespace WixToolset.Dtf.Tools.DDiff |
| 11 | { |
| 12 | public class MsiDiffEngine : IDiffEngine |
| 13 | { |
| 14 | public MsiDiffEngine() |
| 15 | { |
| 16 | } |
| 17 | |
| 18 | protected bool IsMsiDatabase(string file) |
| 19 | { |
| 20 | // TODO: use something smarter? |
| 21 | switch(Path.GetExtension(file).ToLower()) |
| 22 | { |
| 23 | case ".msi": return true; |
| 24 | case ".msm": return true; |
| 25 | case ".pcp": return true; |
| 26 | default : return false; |
| 27 | } |
| 28 | } |
| 29 | |
| 30 | protected bool IsMspPatch(string file) |
| 31 | { |
| 32 | // TODO: use something smarter? |
| 33 | switch(Path.GetExtension(file).ToLower()) |
| 34 | { |
| 35 | case ".msp": return true; |
| 36 | default : return false; |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | public virtual float GetDiffQuality(string diffInput1, string diffInput2, string[] options, IDiffEngineFactory diffFactory) |
| 41 | { |
| 42 | if(diffInput1 != null && File.Exists(diffInput1) && |
| 43 | diffInput2 != null && File.Exists(diffInput2) && |
| 44 | (IsMsiDatabase(diffInput1) || IsMsiDatabase(diffInput2))) |
| 45 | { |
| 46 | return .70f; |
| 47 | } |
| 48 | else if(diffInput1 != null && File.Exists(diffInput1) && |
| 49 | diffInput2 != null && File.Exists(diffInput2) && |
| 50 | (IsMspPatch(diffInput1) || IsMspPatch(diffInput2))) |
| 51 | { |
| 52 | return .60f; |
| 53 | } |
| 54 | else |
| 55 | { |
| 56 | return 0; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | public virtual bool GetDiff(string diffInput1, string diffInput2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) |
| 61 | { |
| 62 | bool difference = false; |
| 63 | Database db1 = new Database(diffInput1, DatabaseOpenMode.ReadOnly); |
| 64 | Database db2 = new Database(diffInput2, DatabaseOpenMode.ReadOnly); |
| 65 | |
| 66 | if(GetSummaryInfoDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; |
| 67 | if(GetDatabaseDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; |
| 68 | if(GetStreamsDiff(db1, db2, options, diffOutput, linePrefix, diffFactory)) difference = true; |
| 69 | |
| 70 | db1.Close(); |
| 71 | db2.Close(); |
| 72 | return difference; |
| 73 | } |
| 74 | |
| 75 | protected bool GetSummaryInfoDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) |
| 76 | { |
| 77 | bool difference = false; |
| 78 | |
| 79 | SummaryInfo summInfo1 = db1.SummaryInfo; |
| 80 | SummaryInfo summInfo2 = db2.SummaryInfo; |
| 81 | if(summInfo1.Title != summInfo2.Title ) { diffOutput.WriteLine("{0}SummaryInformation.Title {{{1}}}->{{{2}}}", linePrefix, summInfo1.Title, summInfo2.Title); difference = true; } |
| 82 | if(summInfo1.Subject != summInfo2.Subject ) { diffOutput.WriteLine("{0}SummaryInformation.Subject {{{1}}}->{{{2}}}", linePrefix, summInfo1.Subject, summInfo2.Subject); difference = true; } |
| 83 | if(summInfo1.Author != summInfo2.Author ) { diffOutput.WriteLine("{0}SummaryInformation.Author {{{1}}}->{{{2}}}", linePrefix, summInfo1.Author, summInfo2.Author); difference = true; } |
| 84 | if(summInfo1.Keywords != summInfo2.Keywords ) { diffOutput.WriteLine("{0}SummaryInformation.Keywords {{{1}}}->{{{2}}}", linePrefix, summInfo1.Keywords, summInfo2.Keywords); difference = true; } |
| 85 | if(summInfo1.Comments != summInfo2.Comments ) { diffOutput.WriteLine("{0}SummaryInformation.Comments {{{1}}}->{{{2}}}", linePrefix, summInfo1.Comments, summInfo2.Comments); difference = true; } |
| 86 | if(summInfo1.Template != summInfo2.Template ) { diffOutput.WriteLine("{0}SummaryInformation.Template {{{1}}}->{{{2}}}", linePrefix, summInfo1.Template, summInfo2.Template); difference = true; } |
| 87 | if(summInfo1.LastSavedBy != summInfo2.LastSavedBy ) { diffOutput.WriteLine("{0}SummaryInformation.LastSavedBy {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSavedBy, summInfo2.LastSavedBy); difference = true; } |
| 88 | if(summInfo1.RevisionNumber != summInfo2.RevisionNumber) { diffOutput.WriteLine("{0}SummaryInformation.RevisionNumber {{{1}}}->{{{2}}}", linePrefix, summInfo1.RevisionNumber, summInfo2.RevisionNumber); difference = true; } |
| 89 | if(summInfo1.CreatingApp != summInfo2.CreatingApp ) { diffOutput.WriteLine("{0}SummaryInformation.CreatingApp {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreatingApp, summInfo2.CreatingApp); difference = true; } |
| 90 | if(summInfo1.LastPrintTime != summInfo2.LastPrintTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastPrintTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastPrintTime, summInfo2.LastPrintTime); difference = true; } |
| 91 | if(summInfo1.CreateTime != summInfo2.CreateTime ) { diffOutput.WriteLine("{0}SummaryInformation.CreateTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.CreateTime, summInfo2.CreateTime); difference = true; } |
| 92 | if(summInfo1.LastSaveTime != summInfo2.LastSaveTime ) { diffOutput.WriteLine("{0}SummaryInformation.LastSaveTime {{{1}}}->{{{2}}}", linePrefix, summInfo1.LastSaveTime, summInfo2.LastSaveTime); difference = true; } |
| 93 | if(summInfo1.CodePage != summInfo2.CodePage ) { diffOutput.WriteLine("{0}SummaryInformation.Codepage {{{1}}}->{{{2}}}", linePrefix, summInfo1.CodePage, summInfo2.CodePage); difference = true; } |
| 94 | if(summInfo1.PageCount != summInfo2.PageCount ) { diffOutput.WriteLine("{0}SummaryInformation.PageCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.PageCount, summInfo2.PageCount); difference = true; } |
| 95 | if(summInfo1.WordCount != summInfo2.WordCount ) { diffOutput.WriteLine("{0}SummaryInformation.WordCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.WordCount, summInfo2.WordCount); difference = true; } |
| 96 | if(summInfo1.CharacterCount != summInfo2.CharacterCount) { diffOutput.WriteLine("{0}SummaryInformation.CharacterCount {{{1}}}->{{{2}}}", linePrefix, summInfo1.CharacterCount, summInfo2.CharacterCount); difference = true; } |
| 97 | if(summInfo1.Security != summInfo2.Security ) { diffOutput.WriteLine("{0}SummaryInformation.Security {{{1}}}->{{{2}}}", linePrefix, summInfo1.Security, summInfo2.Security); difference = true; } |
| 98 | summInfo1.Close(); |
| 99 | summInfo2.Close(); |
| 100 | |
| 101 | return difference; |
| 102 | } |
| 103 | |
| 104 | protected bool GetDatabaseDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) |
| 105 | { |
| 106 | bool difference = false; |
| 107 | |
| 108 | string tempFile = Path.GetTempFileName(); |
| 109 | if(db2.GenerateTransform(db1, tempFile)) |
| 110 | { |
| 111 | difference = true; |
| 112 | |
| 113 | Database db = db1; |
| 114 | db.ViewTransform(tempFile); |
| 115 | |
| 116 | string row, column, change; |
| 117 | using (View view = db.OpenView("SELECT `Table`, `Column`, `Row`, `Data`, `Current` " + |
| 118 | "FROM `_TransformView` ORDER BY `Table`, `Row`")) |
| 119 | { |
| 120 | view.Execute(); |
| 121 | |
| 122 | foreach (Record rec in view) using (rec) |
| 123 | { |
| 124 | column = String.Format("{0} {1}", rec[1], rec[2]); |
| 125 | change = ""; |
| 126 | if (rec.IsNull(3)) |
| 127 | { |
| 128 | row = "<DDL>"; |
| 129 | if (!rec.IsNull(4)) |
| 130 | { |
| 131 | change = "[" + rec[5] + "]: " + DecodeColDef(rec.GetInteger(4)); |
| 132 | } |
| 133 | } |
| 134 | else |
| 135 | { |
| 136 | row = "[" + String.Join(",", rec.GetString(3).Split('\t')) + "]"; |
| 137 | if (rec.GetString(2) != "INSERT" && rec.GetString(2) != "DELETE") |
| 138 | { |
| 139 | column = String.Format("{0}.{1}", rec[1], rec[2]); |
| 140 | change = "{" + rec[5] + "}->{" + rec[4] + "}"; |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | diffOutput.WriteLine("{0}{1,-25} {2} {3}", linePrefix, column, row, change); |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | File.Delete(tempFile); |
| 149 | |
| 150 | return difference; |
| 151 | } |
| 152 | |
| 153 | private string DecodeColDef(int colDef) |
| 154 | { |
| 155 | const int icdLong = 0x0000; |
| 156 | const int icdShort = 0x0400; |
| 157 | const int icdObject = 0x0800; |
| 158 | const int icdString = 0x0C00; |
| 159 | const int icdTypeMask = 0x0F00; |
| 160 | const int icdNullable = 0x1000; |
| 161 | const int icdPrimaryKey = 0x2000; |
| 162 | |
| 163 | string def = ""; |
| 164 | switch(colDef & (icdTypeMask)) |
| 165 | { |
| 166 | case icdLong : def = "LONG"; break; |
| 167 | case icdShort : def = "SHORT"; break; |
| 168 | case icdObject: def = "OBJECT"; break; |
| 169 | case icdString: def = "CHAR[" + (colDef & 0xFF) + "]"; break; |
| 170 | } |
| 171 | if((colDef & icdNullable) != 0) |
| 172 | { |
| 173 | def = def + " NOT NULL"; |
| 174 | } |
| 175 | if((colDef & icdPrimaryKey) != 0) |
| 176 | { |
| 177 | def = def + " PRIMARY KEY"; |
| 178 | } |
| 179 | return def; |
| 180 | } |
| 181 | |
| 182 | protected bool GetStreamsDiff(Database db1, Database db2, string[] options, TextWriter diffOutput, string linePrefix, IDiffEngineFactory diffFactory) |
| 183 | { |
| 184 | bool difference = false; |
| 185 | |
| 186 | IList<string> streams1List = db1.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); |
| 187 | IList<string> streams2List = db2.ExecuteStringQuery("SELECT `Name` FROM `_Streams`"); |
| 188 | string[] streams1 = new string[streams1List.Count]; |
| 189 | string[] streams2 = new string[streams2List.Count]; |
| 190 | streams1List.CopyTo(streams1, 0); |
| 191 | streams2List.CopyTo(streams2, 0); |
| 192 | |
| 193 | IComparer caseInsComp = CaseInsensitiveComparer.Default; |
| 194 | Array.Sort(streams1, caseInsComp); |
| 195 | Array.Sort(streams2, caseInsComp); |
| 196 | |
| 197 | for (int i1 = 0, i2 = 0; i1 < streams1.Length || i2 < streams2.Length; ) |
| 198 | { |
| 199 | int comp; |
| 200 | if (i1 == streams1.Length) |
| 201 | { |
| 202 | comp = 1; |
| 203 | } |
| 204 | else if (i2 == streams2.Length) |
| 205 | { |
| 206 | comp = -1; |
| 207 | } |
| 208 | else |
| 209 | { |
| 210 | comp = caseInsComp.Compare(streams1[i1], streams2[i2]); |
| 211 | } |
| 212 | if(comp < 0) |
| 213 | { |
| 214 | diffOutput.WriteLine("{0}< {1}", linePrefix, streams1[i1]); |
| 215 | i1++; |
| 216 | difference = true; |
| 217 | } |
| 218 | else if(comp > 0) |
| 219 | { |
| 220 | diffOutput.WriteLine("{0}> {1}", linePrefix, streams2[i2]); |
| 221 | i2++; |
| 222 | difference = true; |
| 223 | } |
| 224 | else |
| 225 | { |
| 226 | if(streams1[i1] != ("" + ((char)5) + "SummaryInformation")) |
| 227 | { |
| 228 | string tempFile1 = Path.GetTempFileName(); |
| 229 | string tempFile2 = Path.GetTempFileName(); |
| 230 | |
| 231 | using (View view = db1.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams1[i1]))) |
| 232 | { |
| 233 | view.Execute(); |
| 234 | |
| 235 | using (Record rec = view.Fetch()) |
| 236 | { |
| 237 | rec.GetStream(1, tempFile1); |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | using (View view = db2.OpenView(String.Format("SELECT `Data` FROM `_Streams` WHERE `Name` = '{0}'", streams2[i2]))) |
| 242 | { |
| 243 | view.Execute(); |
| 244 | |
| 245 | using (Record rec = view.Fetch()) |
| 246 | { |
| 247 | rec.GetStream(1, tempFile2); |
| 248 | } |
| 249 | } |
| 250 | |
| 251 | IDiffEngine diffEngine = diffFactory.GetDiffEngine(tempFile1, tempFile2, options); |
| 252 | StringWriter sw = new StringWriter(); |
| 253 | if(diffEngine.GetDiff(tempFile1, tempFile2, options, sw, linePrefix + " ", diffFactory)) |
| 254 | { |
| 255 | diffOutput.WriteLine("{0}{1}", linePrefix, streams1[i1]); |
| 256 | diffOutput.Write(sw.ToString()); |
| 257 | difference = true; |
| 258 | } |
| 259 | |
| 260 | File.Delete(tempFile1); |
| 261 | File.Delete(tempFile2); |
| 262 | } |
| 263 | i1++; |
| 264 | i2++; |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | return difference; |
| 269 | } |
| 270 | |
| 271 | public virtual IDiffEngine Clone() |
| 272 | { |
| 273 | return new MsiDiffEngine(); |
| 274 | } |
| 275 | } |
| 276 | } |