public List<FileRevision> Load(string file = DataFileName)
		{
			var list = new List<FileRevision>();
			using(var r = File.OpenText(file))
			{
				string line;
				while((line = r.ReadLine()) != null)
				{
					var m = _versionRx.Match(line);
					if(!m.Success)
						continue;

					var v = new FileRevision {
						At = new DateTime(long.Parse(m.Groups["at"].Value), DateTimeKind.Utc),
						User = m.Groups["user"].Value,
						FileSpec = m.Groups["spec"].Value,
						VssVersion = int.Parse(m.Groups["ver"].Value),
						Physical = m.Groups["phys"].Value,
						Comment = m.Groups["comment"].Value.Replace('\u0001', '\n')
					};

					list.Add(v);
				}
			}

			return list;
		}
		void UnrecognizedError(FileRevision file, Exception ex = null)
		{
			_log.WriteLine("UNRECOGNIZED ERROR: {0}", file.FileSpec);
			Console.Error.WriteLine("\n!!! Unrecognized error. See logs.\n{0}@{1}", file.FileSpec, file.VssVersion);
			if(ex != null)
			{
				_log.WriteLine(ex.ToString());
				Console.Error.WriteLine(" ERROR: {0}", ex.Message);
			}
		}
		void GetFromVss(FileRevision file)
		{
			// clean destination
			foreach (var tempFile in Directory.GetFiles(_tempDir, "*", SearchOption.AllDirectories))
			{
				try
				{
					File.SetAttributes(tempFile, FileAttributes.Normal);
					File.Delete(tempFile);
				}
				catch (Exception ex)
				{
					Console.WriteLine("\nCan't remove temp file: " + tempFile + "\n" + ex.Message);
				}
			}

			try
			{
				var vssItem = _db.VSSItem[file.FileSpec];

				// move to correct veriosn
				if (vssItem.VersionNumber != file.VssVersion)
					vssItem = vssItem.Version[file.VssVersion];

				var dstFileName = Path.GetFileName(file.FileSpec.TrimStart('$', '/', '\\'));

				var path = Path.Combine(_tempDir, Guid.NewGuid().ToString("N") + "-" + dstFileName);

				try
				{
					vssItem.Get(path, (int)VSSFlags.VSSFLAG_FORCEDIRNO | (int)VSSFlags.VSSFLAG_USERRONO | (int)VSSFlags.VSSFLAG_REPREPLACE);
				}
				catch(Exception ex)
				{
					if (string.IsNullOrWhiteSpace(_options.SSPath))
						throw;

					// special case when physical file not correspond to
					var m = Regex.Match(ex.Message, "File ['\"](?<phys>[^'\"]+)['\"] not found");
					if (!m.Success)
						throw;

					if (m.Groups["phys"].Value == vssItem.Physical)
						throw;

					Console.WriteLine("\nPhysical file mismatch. Try get with ss.exe");

					path = new SSExeHelper(_options.SSPath, _log).Get(file.FileSpec, file.VssVersion, _tempDir);
					if (path == null)
					{
						Console.WriteLine("Get with ss.exe failed");
						throw;
					}
				}

				// in force mode check if file already in cache and coincidence by hash
				if(_options.Force)
				{
					var ce = _cache.GetFileInfo(file.FileSpec, file.VssVersion);
					if(ce != null)
					{
						string hash;

						using(var s = new FileStream(path, FileMode.Open, FileAccess.Read))
							hash = Convert.ToBase64String(_hashAlgo.ComputeHash(s));

						if(hash != ce.Sha1Hash)
						{
							_log.WriteLine("!!! Cache contains different content for: " + file.FileSpec);
							_log.WriteLine("{0} != {1}", hash, ce.Sha1Hash);
							_cache.AddFile(file.FileSpec, file.VssVersion, path, false);
						}
						return;
					}
				}
				_cache.AddFile(file.FileSpec, file.VssVersion, path, false);
			}
			catch (Exception ex)
			{
				if (ex.Message.Contains("does not retain old versions of itself"))
				{
					Console.WriteLine("{0} hasn't retain version {1}.", file.FileSpec, file.VssVersion);

					_cache.AddError(file.FileSpec, file.VssVersion, "not-retained");

					return;
				}

				// last revision shall always present
				// known error
				if (ex.Message.Contains("SourceSafe was unable to finish writing a file.  Check your available disk space, and ask the administrator to analyze your SourceSafe database."))
				{
					Console.Error.WriteLine("\nAbsent file revision: {0}@{1}", file.FileSpec, file.VssVersion);

					_cache.AddError(file.FileSpec, file.VssVersion, "broken-revision");

					return;
				}

				_cache.AddError(file.FileSpec, file.VssVersion, ex.Message);

				UnrecognizedError(file, ex);
			}
		}
		void Process(FileRevision file)
		{
			if (!IsShouldBeProcessed(file.FileSpec))
				return;

			var sw = Stopwatch.StartNew();

			GetFromVss(file);

			sw.Stop();

			lock (_log)
			{
				_log.WriteLine("[{2:s} +{3,-7}ms] Get: {0}@{1}", file.FileSpec, file.VssVersion, DateTimeOffset.Now, sw.ElapsedMilliseconds);
				Console.Write('.');
			}
		}
		public void Build(Options opts, IList<Tuple<string, int>> files, Action<float> progress = null)
		{
			var stopWatch = new Stopwatch();
			stopWatch.Start();

			using (var cache = new VssFileCache(opts.CacheDir + "-revs", opts.SourceSafeIni))
			using(var wr = File.CreateText(DataFileName))
			using(var log = File.CreateText(LogFileName))
			{
				wr.AutoFlush = log.AutoFlush = true;

				var db = opts.DB.Value;

				var findex = 0;
				foreach (var spec in files.Select(t => t.Item1))
				{
					findex++;

					try{
						IVSSItem item = db.VSSItem[spec];
						var head = item.VersionNumber;

						var timestamp = item.VSSVersion.Date.Ticks;

						var cachedData = cache.GetFilePath(spec, head, timestamp);
						if (cachedData != null)
						{
							Console.Write("c");
							Save(wr, Load(cachedData));
							// next file
							continue;
						}

						Console.Write("[{0,5}/{1,5}] {2} ", findex, files.Count, item.Spec);
						if (progress != null)
							progress((float)findex / files.Count);

						var rotationIndex = 0;
						var rotationArray = @"|/-\|/-\".ToCharArray();

						var latestOnly = IsLatestOnly(opts, spec);

						var itemRevisions = new List<FileRevision>();
						foreach (IVSSVersion ver in item.Versions)
						{
							Console.Write("{0}\b", rotationArray[rotationIndex++ % rotationArray.Length]);

							if (ver.Action.StartsWith("Labeled ") || ver.Action.StartsWith("Branched "))
								continue;

							if (!ver.Action.StartsWith("Checked in ") && !ver.Action.StartsWith("Created ") && !ver.Action.StartsWith("Archived ") && !ver.Action.StartsWith("Rollback to"))
							{
								log.WriteLine("Unknown action: " + ver.Action);
							}

							var user = ver.Username.ToLowerInvariant();

							var fileVersionInfo = new FileRevision {
								FileSpec = item.Spec,
								At = ver.Date.ToUniversalTime(),
								Comment = ver.Comment,
								VssVersion = ver.VersionNumber,
								User = user
							};
							try
							{
								// can throw exception, but it is not critical
								fileVersionInfo.Physical = ver.VSSItem.Physical;
							}
							catch (Exception ex)
							{
								Console.WriteLine("ERROR: Get Physical: " + ex.Message);
								log.WriteLine("ERROR: Get Physical: {0}", spec);
								log.WriteLine(ex.ToString());
								fileVersionInfo.Physical = "_UNKNOWN_";
							}
							itemRevisions.Add(fileVersionInfo);

							if (latestOnly)
								break;

							Console.Write('.');
						}

						Console.WriteLine(" ");

						if (itemRevisions.Count > 0)
						{
							// some time date of items wrong, but versions - correct.
							// sort items in correct order and fix dates
							itemRevisions = itemRevisions.OrderBy(i => i.VssVersion).ToList();

							// fix time. make time of each next item greater than all previous
							var notEarlierThan = itemRevisions[0].At;
							for (int i = 1; i < itemRevisions.Count; i++)
							{
								if (itemRevisions[i].At < notEarlierThan)
								{
									itemRevisions[i].At = notEarlierThan + TimeSpan.FromMilliseconds(1);
									itemRevisions[i].Comment += "\n! Time was fixed during VSS -> SVN conversion. Time can be incorrect !\n";
									itemRevisions[i].Comment = itemRevisions[i].Comment.Trim();
								}

								notEarlierThan = itemRevisions[i].At;
							}
						}

						Save(wr, itemRevisions);

						var tempFile = Path.GetTempFileName();
						try
						{
							using (var sw = new StreamWriter(tempFile, false, Encoding.UTF8))
								Save(sw, itemRevisions);

							cache.AddFile(spec, head, timestamp, tempFile, false);
						}
						finally
						{
							if (File.Exists(tempFile))
								File.Delete(tempFile);
						}
					}
					catch(Exception ex)
					{
						Console.WriteLine("ERROR: {0}", spec);
						log.WriteLine("ERROR: {0}", spec);
						log.WriteLine(ex.ToString());
					}
				}
			}

			stopWatch.Stop();
			Console.WriteLine("Build files versions list complete. Take: {0}", stopWatch.Elapsed);
		}