/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using Directory = Lucene.Net.Store.Directory;
using Pattern = System.Text.RegularExpressions.Regex;
namespace Lucene.Net.Index
{
/// A utility class (used by both IndexReader and
/// IndexWriter) to keep track of files that need to be
/// deleted because they are no longer referenced by the
/// index.
///
sealed public class IndexFileDeleter
{
private System.Collections.ArrayList deletable;
private System.Collections.Hashtable pending;
private Directory directory;
private SegmentInfos segmentInfos;
private System.IO.TextWriter infoStream;
public IndexFileDeleter(SegmentInfos segmentInfos, Directory directory)
{
this.segmentInfos = segmentInfos;
this.directory = directory;
}
internal void SetSegmentInfos(SegmentInfos segmentInfos)
{
this.segmentInfos = segmentInfos;
}
internal SegmentInfos GetSegmentInfos()
{
return segmentInfos;
}
internal void SetInfoStream(System.IO.TextWriter infoStream)
{
this.infoStream = infoStream;
}
/// Determine index files that are no longer referenced
/// and therefore should be deleted. This is called once
/// (by the writer), and then subsequently we add onto
/// deletable any files that are no longer needed at the
/// point that we create the unused file (eg when merging
/// segments), and we only remove from deletable when a
/// file is successfully deleted.
///
public void FindDeletableFiles()
{
// Gather all "current" segments:
System.Collections.Hashtable current = new System.Collections.Hashtable();
for (int j = 0; j < segmentInfos.Count; j++)
{
SegmentInfo segmentInfo = (SegmentInfo) segmentInfos[j];
current[segmentInfo.name] = segmentInfo;
}
// Then go through all files in the Directory that are
// Lucene index files, and add to deletable if they are
// not referenced by the current segments info:
System.String segmentsInfosFileName = segmentInfos.GetCurrentSegmentFileName();
IndexFileNameFilter filter = IndexFileNameFilter.GetFilter();
System.String[] files = directory.List();
for (int i = 0; i < files.Length; i++)
{
if (filter.Accept(null, files[i]) && !files[i].Equals(segmentsInfosFileName) && !files[i].Equals(IndexFileNames.SEGMENTS_GEN))
{
System.String segmentName;
System.String extension;
// First remove any extension:
int loc = files[i].IndexOf((System.Char) '.');
if (loc != - 1)
{
extension = files[i].Substring(1 + loc);
segmentName = files[i].Substring(0, (loc) - (0));
}
else
{
extension = null;
segmentName = files[i];
}
// Then, remove any generation count:
loc = segmentName.IndexOf((System.Char) '_', 1);
if (loc != - 1)
{
segmentName = segmentName.Substring(0, (loc) - (0));
}
// Delete this file if it's not a "current" segment,
// or, it is a single index file but there is now a
// corresponding compound file:
bool doDelete = false;
if (!current.ContainsKey(segmentName))
{
// Delete if segment is not referenced:
doDelete = true;
}
else
{
// OK, segment is referenced, but file may still
// be orphan'd:
SegmentInfo info = (SegmentInfo) current[segmentName];
if (filter.IsCFSFile(files[i]) && info.GetUseCompoundFile())
{
// This file is in fact stored in a CFS file for
// this segment:
doDelete = true;
}
else
{
Pattern p = new System.Text.RegularExpressions.Regex("s\\d+");
if ("del".Equals(extension))
{
// This is a _segmentName_N.del file:
if (!files[i].Equals(info.GetDelFileName()))
{
// If this is a seperate .del file, but it
// doesn't match the current del filename for
// this segment, then delete it:
doDelete = true;
}
}
else if (extension != null && extension.StartsWith("s") && p.Match(extension).Success)
{
int field = System.Int32.Parse(extension.Substring(1));
// This is a _segmentName_N.sX file:
if (!files[i].Equals(info.GetNormFileName(field)))
{
// This is an orphan'd separate norms file:
doDelete = true;
}
}
else if ("cfs".Equals(extension) && !info.GetUseCompoundFile())
{
// This is a partially written
// _segmentName.cfs:
doDelete = true;
}
}
}
if (doDelete)
{
AddDeletableFile(files[i]);
if (infoStream != null)
{
infoStream.WriteLine("IndexFileDeleter: file \"" + files[i] + "\" is unreferenced in index and will be deleted on next commit");
}
}
}
}
}
/*
* Some operating systems (e.g. Windows) don't permit a file to be deleted
* while it is opened for read (e.g. by another process or thread). So we
* assume that when a delete fails it is because the file is open in another
* process, and queue the file for subsequent deletion.
*/
internal void DeleteSegments(System.Collections.ArrayList segments)
{
DeleteFiles(); // try to delete files that we couldn't before
for (int i = 0; i < segments.Count; i++)
{
SegmentReader reader = (SegmentReader) segments[i];
if (reader.Directory() == this.directory)
DeleteFiles(reader.Files());
// try to delete our files
else
DeleteFiles(reader.Files(), reader.Directory()); // delete other files
}
}
/// Delete these segments, as long as they are not listed
/// in protectedSegments. If they are, then, instead, add
/// them to the pending set.
///
internal void DeleteSegments(System.Collections.ArrayList segments, System.Collections.Hashtable protectedSegments)
{
DeleteFiles(); // try to delete files that we couldn't before
for (int i = 0; i < segments.Count; i++)
{
SegmentReader reader = (SegmentReader) segments[i];
if (reader.Directory() == this.directory)
{
if (protectedSegments.Contains(reader.GetSegmentName()))
{
AddPendingFiles(reader.Files()); // record these for deletion on commit
}
else
{
DeleteFiles(reader.Files()); // try to delete our files
}
}
else
{
DeleteFiles(reader.Files(), reader.Directory()); // delete other files
}
}
}
internal void DeleteFiles(System.Collections.ArrayList files, Directory directory)
{
for (int i = 0; i < files.Count; i++)
directory.DeleteFile((System.String) files[i]);
}
internal void DeleteFiles(System.Collections.ArrayList files)
{
DeleteFiles(); // try to delete files that we couldn't before
for (int i = 0; i < files.Count; i++)
{
DeleteFile((System.String) files[i]);
}
}
internal void DeleteFile(System.String file)
{
try
{
directory.DeleteFile(file); // try to delete each file
}
catch (System.IO.IOException e)
{
// if delete fails
if (directory.FileExists(file))
{
if (infoStream != null)
{
infoStream.WriteLine("IndexFileDeleter: unable to remove file \"" + file + "\": " + e.ToString() + "; Will re-try later.");
}
AddDeletableFile(file); // add to deletable
}
}
}
internal void ClearPendingFiles()
{
pending = null;
}
/*
Record that the files for these segments should be
deleted, once the pending deletes are committed.
*/
internal void AddPendingSegments(System.Collections.ArrayList segments)
{
for (int i = 0; i < segments.Count; i++)
{
SegmentReader reader = (SegmentReader) segments[i];
if (reader.Directory() == this.directory)
{
AddPendingFiles(reader.Files());
}
}
}
/*
Record list of files for deletion, but do not delete
them until commitPendingFiles is called.
*/
internal void AddPendingFiles(System.Collections.ArrayList files)
{
for (int i = 0; i < files.Count; i++)
{
AddPendingFile((System.String) files[i]);
}
}
/*
Record a file for deletion, but do not delete it until
commitPendingFiles is called.
*/
internal void AddPendingFile(System.String fileName)
{
if (pending == null)
{
pending = new System.Collections.Hashtable();
}
if (pending.ContainsKey(fileName) == false)
{
pending.Add(fileName, fileName);
}
}
internal void CommitPendingFiles()
{
if (pending != null)
{
if (deletable == null)
{
deletable = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
}
System.Collections.IEnumerator it = pending.GetEnumerator();
while (it.MoveNext())
{
deletable.Add(((System.Collections.DictionaryEntry)(it.Current)).Value);
}
pending = null;
DeleteFiles();
}
}
internal void AddDeletableFile(System.String fileName)
{
if (deletable == null)
{
deletable = System.Collections.ArrayList.Synchronized(new System.Collections.ArrayList(10));
}
deletable.Add(fileName);
}
public void DeleteFiles()
{
if (deletable != null)
{
System.Collections.ArrayList oldDeletable = deletable;
deletable = null;
DeleteFiles(oldDeletable); // try to delete deletable
}
}
}
}