2 * Copyright 2004 The Apache Software Foundation
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 using Constants
= Lucene
.Net
.Util
.Constants
;
18 using System
.Diagnostics
; // FIXED joeshaw@novell.com 10 Jan 2005 - for lock debugging
19 namespace Lucene
.Net
.Store
22 /// <summary> Straightforward implementation of {@link Directory} as a directory of files.
23 /// <p>If the system property 'disableLuceneLocks' has the String value of
24 /// "true", lock creation will be disabled.
27 /// <seealso cref="Directory">
29 /// <author> Doug Cutting
31 public sealed class FSDirectory
: Directory
33 private class AnonymousClassLock
: Lock
35 public AnonymousClassLock(System
.IO
.FileInfo lockFile
, FSDirectory enclosingInstance
)
37 InitBlock(lockFile
, enclosingInstance
);
39 private void InitBlock(System
.IO
.FileInfo lockFile
, FSDirectory enclosingInstance
)
41 this.lockFile
= lockFile
;
42 this.enclosingInstance
= enclosingInstance
;
44 private System
.IO
.FileInfo lockFile
;
45 private FSDirectory enclosingInstance
;
46 public FSDirectory Enclosing_Instance
50 return enclosingInstance
;
54 public override bool Obtain()
56 if (Lucene
.Net
.Store
.FSDirectory
.DISABLE_LOCKS
)
59 // ADDED fhedberg@novell.com 16 Jun 2005
60 if (Enclosing_Instance
.DisableLocks
)
64 if (System
.IO
.File
.Exists(Enclosing_Instance
.lockDir
.FullName
))
67 tmpBool
= System
.IO
.Directory
.Exists(Enclosing_Instance
.lockDir
.FullName
);
72 System
.IO
.Directory
.CreateDirectory(Enclosing_Instance
.lockDir
.FullName
);
76 throw new System
.IO
.IOException("Cannot create lock directory: " + Enclosing_Instance
.lockDir
);
80 // ADDED trow@novell.com 17 Mar 2005: Use Mono.Posix for lock creation
82 bool obtainedLock
= false;
84 int fd
= Mono
.Posix
.Syscall
.open (lockFile
.FullName
,
85 Mono
.Posix
.OpenFlags
.O_CREAT
86 | Mono
.Posix
.OpenFlags
.O_EXCL
,
87 Mono
.Posix
.FileMode
.S_IRUSR
88 | Mono
.Posix
.FileMode
.S_IWUSR
);
91 Mono
.Posix
.Syscall
.close (fd
);
100 System
.IO
.FileStream createdFile
= lockFile
.Create();
110 Log ("{0} lock {1}", obtainedLock
? "Obtained" : "Could not obtain", lockFile
.FullName
);
114 public override void Release()
116 if (Lucene
.Net
.Store
.FSDirectory
.DISABLE_LOCKS
)
119 // ADDED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
120 if (Enclosing_Instance
.DisableLocks
)
124 if (System
.IO
.File
.Exists(lockFile
.FullName
))
126 System
.IO
.File
.Delete(lockFile
.FullName
);
129 else if (System
.IO
.Directory
.Exists(lockFile
.FullName
))
131 System
.IO
.Directory
.Delete(lockFile
.FullName
);
137 // ADDED joeshaw@novell.com 10 Jan 2005 - lock debugging
139 Log ("Released lock {0}", lockFile
.FullName
);
141 Log ("Failed to release lock {0}", lockFile
.FullName
);
143 public override bool IsLocked()
145 if (Lucene
.Net
.Store
.FSDirectory
.DISABLE_LOCKS
)
148 // ADDED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
149 if (Enclosing_Instance
.DisableLocks
)
153 if (System
.IO
.File
.Exists(lockFile
.FullName
))
156 tmpBool
= System
.IO
.Directory
.Exists(lockFile
.FullName
);
160 public override System
.String
ToString()
162 return "Lock@" + lockFile
;
165 /// <summary>This cache of directories ensures that there is a unique Directory
166 /// instance per path, so that synchronization on the Directory can be used to
167 /// synchronize access between readers and writers.
169 /// This should be a WeakHashMap, so that entries can be GC'd, but that would
170 /// require Java 1.2. Instead we use refcounts...
172 private static readonly System
.Collections
.Hashtable DIRECTORIES
= System
.Collections
.Hashtable
.Synchronized(new System
.Collections
.Hashtable());
174 private static readonly bool DISABLE_LOCKS
;
177 /// Get the name of the directory to use for temporary files,
178 /// and create that directory if it doesn't already exist
180 /// FIXED trow@ximian.com 14 May 2004 Give us control over where locks are stored
181 /// FIXED trow@ximian.com 12 Sep 2004 make TempDirectoryName not be static
182 private String tempDirectoryName
= null;
183 public String TempDirectoryName
{
185 if (tempDirectoryName
== null) {
186 String user_name
= Environment
.GetEnvironmentVariable("USER");
187 if (user_name
== null)
188 user_name
= "unknown";
189 TempDirectoryName
= "/tmp/" + user_name
+ "-lucene.net";
191 return tempDirectoryName
;
195 tempDirectoryName
= value;
196 if (! System
.IO
.Directory
.Exists (tempDirectoryName
))
197 System
.IO
.Directory
.CreateDirectory (tempDirectoryName
);
201 // ADDED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
203 private bool disable_locks
= false;
205 public bool DisableLocks
{
206 get { return disable_locks; }
207 set { disable_locks = value; }
210 private static System
.Security
.Cryptography
.MD5 DIGESTER
;
212 /// <summary>A buffer optionally used in renameTo method </summary>
213 private byte[] buffer
= null;
215 /// <summary>Returns the directory instance for the named location.
217 /// <p>Directories are cached, so that, for a given canonical path, the same
218 /// FSDirectory instance will always be returned. This permits
219 /// synchronization on directories.
222 /// <param name="path">the path to the directory.
224 /// <param name="create">if true, create, or erase any existing contents.
226 /// <returns> the FSDirectory for the named file.
229 /// CHANGED trow@novell.com 17 Mar 2005 set tmp dir at creation-time
230 /// CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
231 public static FSDirectory
GetDirectory(System
.String path
, System
.String tmpDir
, bool create
)
233 return GetDirectory(new System
.IO
.FileInfo(path
), tmpDir
, create
, false);
236 // ADDED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
237 public static FSDirectory
GetDirectory(System
.String path
, System
.String tmpDir
, bool create
, bool disable_locks
)
239 return GetDirectory(new System
.IO
.FileInfo(path
), tmpDir
, create
, disable_locks
);
242 // CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
243 public static FSDirectory
GetDirectory(System
.String path
, bool create
)
245 return GetDirectory(new System
.IO
.FileInfo(path
), null, create
, false);
248 /// <summary>Returns the directory instance for the named location.
250 /// <p>Directories are cached, so that, for a given canonical path, the same
251 /// FSDirectory instance will always be returned. This permits
252 /// synchronization on directories.
255 /// <param name="file">the path to the directory.
257 /// <param name="create">if true, create, or erase any existing contents.
259 /// <returns> the FSDirectory for the named file.
261 /// CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
262 public static FSDirectory
GetDirectory(System
.IO
.FileInfo file
, System
.String tmpDir
, bool create
, bool disable_locks
)
264 file
= new System
.IO
.FileInfo(file
.FullName
);
266 lock (DIRECTORIES
.SyncRoot
)
268 dir
= (FSDirectory
) DIRECTORIES
[file
];
271 // CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
272 dir
= new FSDirectory(file
, tmpDir
, create
, disable_locks
);
273 DIRECTORIES
[file
] = dir
;
287 // CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
288 public static FSDirectory
GetDirectory(System
.IO
.FileInfo file
, bool create
)
290 return GetDirectory (file
, null, create
, false);
293 private System
.IO
.FileInfo directory
= null;
294 private int refCount
;
295 private System
.IO
.FileInfo lockDir
;
297 /// CHANGED fhedberg@novell.com 16 Jun 2005 - disable locks per directory
298 private FSDirectory(System
.IO
.FileInfo path
, string tmpDir
, bool create
, bool disable_locks
)
301 this.disable_locks
= disable_locks
;
303 // FIXED joeshaw@novell.com 10 Jan 2005 Use TempDirectoryName to find where locks live
304 // FIXED trow@novell.com 17 Mar 2005 A fix on the fix
306 TempDirectoryName
= tmpDir
;
307 lockDir
= new System
.IO
.FileInfo (TempDirectoryName
);
313 if (!System
.IO
.Directory
.Exists(directory
.FullName
))
314 throw new System
.IO
.IOException(path
+ " not a directory");
317 private void Create()
322 if (System
.IO
.File
.Exists(directory
.FullName
))
325 tmpBool
= System
.IO
.Directory
.Exists(directory
.FullName
);
330 System
.IO
.Directory
.CreateDirectory(directory
.FullName
);
334 throw new System
.IO
.IOException("Cannot create directory: " + directory
);
338 System
.String
[] files
= System
.IO
.Directory
.GetFileSystemEntries(directory
.FullName
); // clear old files
339 for (int i
= 0; i
< files
.Length
; i
++)
341 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(files
[i
]);
343 if (System
.IO
.File
.Exists(file
.FullName
))
345 System
.IO
.File
.Delete(file
.FullName
);
348 else if (System
.IO
.Directory
.Exists(file
.FullName
))
350 System
.IO
.Directory
.Delete(file
.FullName
);
356 throw new System
.IO
.IOException("Cannot delete " + files
[i
]);
359 System
.String lockPrefix
= GetLockPrefix().ToString(); // clear old locks
360 files
= System
.IO
.Directory
.GetFileSystemEntries(lockDir
.FullName
);
361 for (int i
= 0; i
< files
.Length
; i
++)
363 if (!files
[i
].StartsWith(lockPrefix
))
365 System
.IO
.FileInfo lockFile
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(lockDir
.FullName
, files
[i
]));
367 if (System
.IO
.File
.Exists(lockFile
.FullName
))
369 System
.IO
.File
.Delete(lockFile
.FullName
);
372 else if (System
.IO
.Directory
.Exists(lockFile
.FullName
))
374 System
.IO
.Directory
.Delete(lockFile
.FullName
);
380 throw new System
.IO
.IOException("Cannot delete " + files
[i
]);
385 /// <summary>Returns an array of strings, one for each file in the directory. </summary>
386 public override System
.String
[] List()
388 return System
.IO
.Directory
.GetFileSystemEntries(directory
.FullName
);
391 /// <summary>Returns true iff a file with the given name exists. </summary>
392 public override bool FileExists(System
.String name
)
394 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
396 if (System
.IO
.File
.Exists(file
.FullName
))
399 tmpBool
= System
.IO
.Directory
.Exists(file
.FullName
);
403 /// <summary>Returns the time the named file was last modified. </summary>
404 public override long FileModified(System
.String name
)
406 // FIXED joeshaw@novell.com 24 Jun 2005 - Use UTC
407 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
408 return ((file
.LastWriteTimeUtc
.Ticks
- 621355968000000000) / 10000);
411 /// <summary>Returns the time the named file was last modified. </summary>
412 public static long FileModified(System
.IO
.FileInfo directory
, System
.String name
)
414 // FIXED joeshaw@novell.com 24 Jun 2005 - Use UTC
415 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
416 return ((file
.LastWriteTimeUtc
.Ticks
- 621355968000000000) / 10000);
419 /// <summary>Set the modified time of an existing file to now. </summary>
420 public override void TouchFile(System
.String name
)
422 // FIXED joeshaw@novell.com 24 Jun 2005 - Use UTC
423 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
424 file
.LastWriteTimeUtc
= System
.DateTime
.UtcNow
;
427 /// <summary>Returns the length in bytes of a file in the directory. </summary>
428 public override long FileLength(System
.String name
)
430 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
431 return file
.Exists
? file
.Length
: 0;
434 /// <summary>Removes an existing file in the directory. </summary>
435 public override void DeleteFile(System
.String name
)
437 System
.IO
.FileInfo file
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
));
439 if (System
.IO
.File
.Exists(file
.FullName
))
441 System
.IO
.File
.Delete(file
.FullName
);
444 else if (System
.IO
.Directory
.Exists(file
.FullName
))
446 System
.IO
.Directory
.Delete(file
.FullName
);
452 throw new System
.IO
.IOException("Cannot delete " + name
);
455 /// <summary>Renames an existing file in the directory. </summary>
456 public override void RenameFile(System
.String
from, System
.String to
)
460 System
.IO
.FileInfo old
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, from));
461 System
.IO
.FileInfo nu
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, to
));
463 /* This is not atomic. If the program crashes between the call to
464 delete() and the call to renameTo() then we're screwed, but I've
465 been unable to figure out how else to do this... */
468 if (System
.IO
.File
.Exists(nu
.FullName
))
471 tmpBool
= System
.IO
.Directory
.Exists(nu
.FullName
);
475 if (System
.IO
.File
.Exists(nu
.FullName
))
477 System
.IO
.File
.Delete(nu
.FullName
);
480 else if (System
.IO
.Directory
.Exists(nu
.FullName
))
482 System
.IO
.Directory
.Delete(nu
.FullName
);
488 throw new System
.IO
.IOException("Cannot delete " + to
);
491 // Rename the old file to the new one. Unfortunately, the renameTo()
492 // method does not work reliably under some JVMs. Therefore, if the
493 // rename fails, we manually rename by copying the old file to the new one
496 old
.MoveTo(nu
.FullName
);
498 catch (System
.Exception ex
)
500 System
.IO
.BinaryReader in_Renamed
= null;
501 System
.IO
.Stream out_Renamed
= null;
504 in_Renamed
= new System
.IO
.BinaryReader(System
.IO
.File
.Open(old
.FullName
, System
.IO
.FileMode
.Open
, System
.IO
.FileAccess
.Read
));
505 // FIXED trow@novell.com 18 Jul 2005
506 // Open the FileStream w/ FileShare.ReadWrite.
507 out_Renamed
= new System
.IO
.FileStream(nu
.FullName
, System
.IO
.FileMode
.Create
, System
.IO
.FileAccess
.Write
, System
.IO
.FileShare
.ReadWrite
);
508 // see if the buffer needs to be initialized. Initialization is
509 // only done on-demand since many VM's will never run into the renameTo
510 // bug and hence shouldn't waste 1K of mem for no reason.
513 buffer
= new byte[1024];
516 len
= in_Renamed
.Read(buffer
, 0, buffer
.Length
);
517 out_Renamed
.Write(buffer
, 0, len
);
519 // FIXED trow@novell.com 18 Feb 2005
520 // Commented out unused variables tmpBool3 and generatedAux
521 // to avoid compiler warnings
523 // delete the old file.
525 if (System
.IO
.File
.Exists(old
.FullName
))
527 System
.IO
.File
.Delete(old
.FullName
);
530 else if (System
.IO
.Directory
.Exists(old
.FullName
))
532 System
.IO
.Directory
.Delete(old
.FullName
);
537 //bool generatedAux = tmpBool3;
539 catch (System
.IO
.IOException e
)
541 throw new System
.IO
.IOException("Cannot rename " + from + " to " + to
);
545 if (in_Renamed
!= null)
551 catch (System
.IO
.IOException e
)
553 throw new System
.SystemException("Cannot close input stream: " + e
.Message
);
556 if (out_Renamed
!= null)
562 catch (System
.IO
.IOException e
)
564 throw new System
.SystemException("Cannot close output stream: " + e
.Message
);
572 /// <summary>Creates a new, empty file in the directory with the given name.
573 /// Returns a stream writing this file.
575 public override OutputStream
CreateFile(System
.String name
)
577 return new FSOutputStream(new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
)));
580 /// <summary>Returns a stream reading an existing file. </summary>
581 public override InputStream
OpenFile(System
.String name
)
583 return new FSInputStream(new System
.IO
.FileInfo(System
.IO
.Path
.Combine(directory
.FullName
, name
)));
586 // ADDED trow 4 June 2004
587 static public Beagle
.Util
.Logger Logger
= null;
588 //static public Beagle.Util.Logger Logger = Beagle.Util.Logger.Log;
589 static private void Log (string format
, params object[] args
)
592 Logger
.Debug (format
, args
);
594 static private void Log (Exception e
)
600 /// <summary> So we can do some byte-to-hexchar conversion below</summary>
601 private static readonly char[] HEX_DIGITS
= new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}
;
603 /// <summary>Constructs a {@link Lock} with the specified name. Locks are implemented
604 /// with {@link File#createNewFile() }.
606 /// <p>In JDK 1.1 or if system property <I>disableLuceneLocks</I> is the
607 /// string "true", locks are disabled. Assigning this property any other
608 /// string will <B>not</B> prevent creation of lock files. This is useful for
609 /// using Lucene on read-only medium, such as CD-ROM.
612 /// <param name="name">the name of the lock file
614 /// <returns> an instance of <code>Lock</code> holding the lock
616 public override Lock
MakeLock(System
.String name
)
618 System
.Text
.StringBuilder buf
= GetLockPrefix();
622 // create a lock file
623 System
.IO
.FileInfo lockFile
= new System
.IO
.FileInfo(System
.IO
.Path
.Combine(lockDir
.FullName
, buf
.ToString()));
625 return new AnonymousClassLock(lockFile
, this);
628 private System
.Text
.StringBuilder
GetLockPrefix()
630 System
.String dirName
; // name to be hashed
633 dirName
= directory
.FullName
;
635 catch (System
.IO
.IOException e
)
637 throw new System
.SystemException(e
.ToString());
643 digest
= DIGESTER
.ComputeHash(System
.Text
.Encoding
.UTF8
.GetBytes(dirName
));
645 System
.Text
.StringBuilder buf
= new System
.Text
.StringBuilder();
646 buf
.Append("lucene-");
647 for (int i
= 0; i
< digest
.Length
; i
++)
650 buf
.Append(HEX_DIGITS
[(b
>> 4) & 0xf]);
651 buf
.Append(HEX_DIGITS
[b
& 0xf]);
657 /// <summary>Closes the store to future operations. </summary>
658 public override void Close()
664 lock (DIRECTORIES
.SyncRoot
)
666 DIRECTORIES
.Remove(directory
);
672 public System
.IO
.FileInfo
GetFile()
677 /// <summary>For debug output. </summary>
678 public override System
.String
ToString()
680 return "FSDirectory@" + directory
;
684 DISABLE_LOCKS
= System
.Configuration
.ConfigurationSettings
.AppSettings
.Get("disableLuceneLocks") != null;
688 DIGESTER
= System
.Security
.Cryptography
.MD5
.Create();
690 catch (System
.Exception e
)
692 throw new System
.SystemException(e
.ToString());
699 sealed public class FSInputStream
: InputStream
, System
.ICloneable
701 internal class Descriptor
: System
.IO
.BinaryReader
703 private void InitBlock(FSInputStream enclosingInstance
)
705 this.enclosingInstance
= enclosingInstance
;
707 private FSInputStream enclosingInstance
;
708 public FSInputStream Enclosing_Instance
712 return enclosingInstance
;
717 //private String name;
720 public long position
;
721 public Descriptor(FSInputStream enclosingInstance
, System
.IO
.FileInfo file
, System
.IO
.FileAccess fileAccess
)
722 : base(new System
.IO
.FileStream(file
.FullName
, System
.IO
.FileMode
.Open
, fileAccess
, System
.IO
.FileShare
.ReadWrite
))
726 //{{}}// public Descriptor(FSInputStream enclosingInstance, System.IO.FileInfo file, System.String mode) : base(file, mode)
728 //{{}}// InitBlock(enclosingInstance);
730 //{{}}// //name = file.ToString();
731 //{{}}// //debug_printInfo("OPEN");
737 //public void close() throws IOException {
738 // debug_printInfo("CLOSE");
742 //private void debug_printInfo(String op) {
743 // try { throw new Exception(op + " <" + name + ">");
744 // } catch (Exception e) {
745 // java.io.StringWriter sw = new java.io.StringWriter();
746 // java.io.PrintWriter pw = new java.io.PrintWriter(sw);
747 // e.printStackTrace(pw);
748 // System.out.println(sw.getBuffer().ToString());
754 internal Descriptor file
= null;
755 public /*internal*/ bool isClone
;
757 public FSInputStream(System
.IO
.FileInfo path
)
759 file
= new Descriptor(this, path
, System
.IO
.FileAccess
.Read
);
760 length
= file
.BaseStream
.Length
;
763 /// <summary>InputStream methods </summary>
764 public override void ReadInternal(byte[] b
, int offset
, int len
)
768 long position
= GetFilePointer();
769 if (position
!= file
.position
)
771 file
.BaseStream
.Seek(position
, System
.IO
.SeekOrigin
.Begin
);
772 file
.position
= position
;
777 int i
= file
.Read(b
, offset
+ total
, len
- total
);
779 throw new System
.IO
.IOException("read past EOF");
787 public override void Close()
789 if (!isClone
&& file
!= null)
791 System
.GC
.SuppressFinalize(this);
794 /// <summary>Random-access methods </summary>
795 public override void SeekInternal(long position
)
801 Close(); // close the file
804 public override System
.Object
Clone()
806 FSInputStream clone
= (FSInputStream
) base.Clone();
807 clone
.isClone
= true;
811 /// <summary>Method used for testing. Returns true if the underlying
812 /// file descriptor is valid.
814 public /*internal*/ bool IsFDValid()
816 return file
.BaseStream
.CanRead
;
821 sealed class FSOutputStream
: OutputStream
823 internal System
.IO
.BinaryWriter file
= null;
825 public FSOutputStream(System
.IO
.FileInfo path
)
827 file
= new System
.IO
.BinaryWriter(new System
.IO
.FileStream(path
.FullName
, System
.IO
.FileMode
.OpenOrCreate
, System
.IO
.FileAccess
.Write
, System
.IO
.FileShare
.ReadWrite
));
830 /// <summary>output methods: </summary>
831 public override void FlushBuffer(byte[] b
, int size
)
833 file
.Write(b
, 0, size
);
835 public override void Close()
839 System
.GC
.SuppressFinalize(this);
842 /// <summary>Random-access methods </summary>
843 public override void Seek(long pos
)
846 file
.BaseStream
.Seek(pos
, System
.IO
.SeekOrigin
.Begin
);
848 public override long Length()
850 return file
.BaseStream
.Length
;
855 file
.Close(); // close the file