http://esiyo.net - uordek.essiyoo.each { |yazi| esiyo.net << yazi }

Dosya İşlemleri (Karşılaştırma)

05.09.2007, yazan Tankut ŞENTÜRK

Merhabalar,

Öncelikle karşılaştırma derken, nasıl bir karşılaştırma yapacağımızı anlatmalıyım. Performans işlemleri karşılaştırma kriterlerim içerisinde bulunmuyor, bundan hiç bahsetmeyeceğim. Ama kodun okunabilirliği, hızlı yazılması, hatalara karşı ne kadar duyarlı olduğu ve tabii ki en önemlisi tekrar kullanabilirliği.

Aslında bu son nokta yani 'tekrar kullanılabilirlik' başlı başına bir yazı konusu ama buna şimdilik pek değinmeyeceğim. Başlıkta yazdığı gibi dosya işlemlerini karşılaştıracağım.

Arşılaştırmayı sadece VBScript, JScript ve Ruby arasında yapacağım, neden mi ? hali hazırda yapmıştım da ondan.. bu yazıyı yazacağım diye oturup program yazmadım. Önce programları yazdım, sonra yazıyı yazmak aklıma geldi. Bu nedenle neden diye sormayın. :)

Ama belki daha sonraki zamanlarda Java ve C# versiyonlarının karşılaştırmalarını da eklerim.

Öncelikle sizlere problemden bahsedeyim.

Belirli bir server üzerinde ps ve txt (post script ve text) dosyaları online bir uygulama tarafından oluşturuluyor, daha sonrasında online uygulama üzerinden kullanıcı bu dosyaları temizlemeyi unutuyor ve dosya sisteminin şişmesi ile beraber performans problemleri ortaya çıkıyor. Bu nedenle dosyaların gün bazında ömürlerinin olmasına ve ömrünü doldurmuş olan dosyalarında sistem tarafından silinmesine, silme işlemi esnasında log almasını ve bu loglarında aynı ömür kuralına tabii olmasını istiyoruz... işte bu program bu işi yapacak.. günde bir kez çalışacak ve bu işlemi yapacak. Microsoft Windows sistemde çalışmasını istediğimiz için VBScript ve JScript'de (JavaScript'in bire bir aynı klonu) ve platform bağımsız olan Ruby'de yazıldı. İşte Ruby örneği;

# Sabitler büyük harf ile tanımlanır.
SOURCE_DRIVE_NAME = 'C'
SOURCE_FOLDER_NAME = 'C:\FtpData'
SOURCE_FILE_TYPES = 'txt,ps'
LOG_FILE_NAME = 'Schedule_Job.txt'
EXPIRY_DAY = 7

source_files = nil
if SOURCE_FILE_TYPES.include?(",")
    source_files = File.join("**", "*.{#{SOURCE_FILE_TYPES}}")
else
    source_files = File.join("**", "*.#{SOURCE_FILE_TYPES}")
end
# Belirtilen dizin mevcut mu ?
if File.directory?(SOURCE_FOLDER_NAME) == false
    raise Exception.new("Folder does not exist (#{SOURCE_FOLDER_NAME})")
end
# Loglama işlemi için One Click-installer ile otomatik olarak gelen log4r kullanildi.
require "log4r"

toDay = Time.now
log = Log4r::Logger.new('IS19660')
logFile = Log4r::FileOutputter.new('IS19660', :filename => SOURCE_FOLDER_NAME + '\\' + toDay.strftime('%Y%m%d%H%M%S') + '_' + LOG_FILE_NAME, :trunc => true)
log.outputters = logFile
log.level = Log4r::INFO

fileCTime = nil
diff = 0.00
# Dizine geçildi
Dir.chdir(SOURCE_FOLDER_NAME)
# Dizin içerisinde ilgili dosyalar bulundu ve üzerinden gezildi.
Dir.glob(source_files) do |fileName|
    fullFileName = SOURCE_FOLDER_NAME + "\\" + fileName.gsub('/','\\')
    # Bulunan dosyanın dizin olmadığı garanti altına alındı.. bazı durumda
    # örnek dosya isminde 2 tane '.' olması durumunda bize dosya olarak dizinleri veriyor.
    next if FileTest.directory?(fullFileName)
    fileCTime = File.ctime(fullFileName)
    diff = (toDay - fileCTime) / 86400
    if diff.floor>= EXPIRY_DAY
        begin
            File.delete(fullFileName)
        rescue
            log.info "Error at #{fullFileName}: #{$!.message}"
        else
            log.info "Deleted : #{fullFileName} at #{fileCTime.strftime('%d/%m/%Y %H:%M:%S')}"
        end
    else
        log.info "Skipped (Day) : #{fullFileName} at #{fileCTime.strftime('%d/%m/%Y %H:%M:%S')}"
    end
end

Görüldüğü gibi toplamı 45 satır civarında ve işin yapıldığı kısım yaklaşık 15 satır. çok efektif ve anlaşılması çok kolay.

İşte sizlere VBScript hali.

' Tüm değişkenlerin tanımlı olması şarttır.
Option Explicit

Const SourceDriveName = "C"
Const SourceFolderName = "C:\FtpData"
Const SourceFileTypes = "txt,ps"
Const LogFileName = "Schedule_Job.txt"
Const ExpiryDay = 7

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Constants for opening files
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Const OpenFileForReading = 1
Const OpenFileForWriting = 2
Const OpenFileForAppending = 8

Sub WriteLog(Str)
    If Str <> "" Then
        LogStr = LogStr & Str & NewLine
    Else
        LogStr = LogStr & Err.Number & " - " & Err.Description & NewLine
        LogStr = LogStr & Err.Source & NewLine
        Err.Clear
    End If
End Sub

Sub ProcessFile(Folder)
    Dim Files, File, Diff, Index
    Dim FileExtension, TempArr, Skip

    Set Files = Folder.Files
    If Files.Count> 0 Then
        For Each File In Files
            TempArr = Split(File.Name, ".")
            FileExtension = TempArr(UBound(TempArr))
            Skip = True
            For Index = LBound(FileTypes) To UBound(FileTypes)
                If FileTypes(Index) = FileExtension Then
                    Skip = False
                    Exit For
                End If
            Next
           
            If Skip = False Then
                Diff = DateDiff("d", File.DateCreated, ToDay)
                If Diff>= ExpiryDay Then
                    WriteLog "Deleted: " & File.Path & " -- "  & File.DateCreated & " -- "  & ToDay & " -- "  & Diff
                    File.Delete
                    If Err.Number <> 0 Then
                        WriteLog ""
                    End If
                Else
                    WriteLog "Skipped (Day) : " & File.Path & " -- "  & File.DateCreated & " -- "  & ToDay & " -- "  & Diff
                End If
            Else
                WriteLog "Skipped (Extension) : " & File.Path & " -- "  & File.DateCreated & " -- "  & ToDay
            End If
        Next
    End If
End Sub

Sub ProcessFolder(ParentFolder)
    Dim SubFolder, Folder
   
    Set SubFolder = ParentFolder.SubFolders
    If SubFolder.Count> 0 Then
        For Each Folder In SubFolder
            ProcessFolder Folder
        Next
    End If
    ProcessFile ParentFolder
End Sub

Sub Main()
    On Error Resume Next
    Dim SourceFolder, ToDayStr
    ToDayStr = Year(ToDay) & Right("00" & Month(ToDay), 2) & Right("00" & Day(ToDay), 2) & Right("00" & Hour(Time), 2) & Right("00" & Minute(Time), 2) & Right("00" & Second(Time), 2)
    Set FSO = CreateObject("Scripting.FileSystemObject")

    If FSO.DriveExists(SourceDriveName) AND FSO.FolderExists(SourceFolderName) Then
        Set SourceFolder = FSO.GetFolder(SourceFolderName)
        ProcessFolder SourceFolder

        If Err.Number <> 0 Then
            WriteLog ""
        End If

        If Trim(LogStr) <> "" Then
            Dim LogFile
            Set LogFile = FSO.CreateTextFile(SourceFolderName & "\" & ToDayStr & "_" & LogFileName, OpenFileForWriting, True)
            LogFile.Write LogStr
            LogFile.Close
        End If
    Else
        Dim LogFileInC

        Set LogFileInC = FSO.CreateTextFile("C:\" & ToDayStr & "_" & LogFileName, OpenFileForWriting, True)
        LogFileInC.Write LogStr
        LogFileInC.Write Err.Number & "-" & Err.Description & NewLine
        LogFileInC.Write Err.Source & NewLine
        LogFileInC.Close
    End If
End Sub

Dim NewLine, TabChar, LogStr, ToDay
Dim FSO, FileTypes

NewLine = Chr(10)
TabChar = Chr(9)
LogStr = ""
ToDay = Date
FileTypes = Split(SourceFileTypes, ",")

Call Main()

Yaklaşık 115 satır, programın kendisi yaklaşık 80 satır kadar, içerisinde recursive bir method mevcut, bu nedenle takip etmesi ve anlaması oldukça zor, fakat kütüphane desteği yüksek. Bununla beraber çok ofansif bir program, herhangi bir hata yakalama yok, 'On Error Resume Next' ile hayatın çok güzel olduğu varsayılmış.

Şimdi JScript (JavaScript versiyonu)

var sourceDriveName = 'C';
var sourceFolderName = 'C:\\FtpData';
var sourceFileTypes = 'txt,ps';
var logFileName = 'Schedule_Job.txt';
var expiryDay = 7;
/*
constants for opening files
*/

var OpenFileForReading = 1;
var OpenFileForWriting = 2;
var OpenFileForAppending = 8;

var logStr = '';
var fileTypes = new Array();
var toDay = new Date();

function print(str) {
    logStr += (str + '\n');
}
// En azından sınıflar çalışma zamanında değiştirilebiliyor.
Number.prototype.fixDigit = function (fixDigit$) {
    if (arguments.length != 1) {
        throw new Error('Wrong number of arguments');
    }
    var length = arguments[0];
    var retValue = this.toString();
    var currentLength = retValue.length;
    if (currentLength>= length) {
        return retValue;
    }
    for (var index = 0; index <length - currentLength; index++) {
        retValue = '0' + retValue;
    }
    return retValue;
}
// format işlemini kendi kullanımıma göre yazdım.
Date.prototype.format = function(format$) {
    return this.getFullYear().toString() + this.getMonth().fixDigit(2) + this.getDate().fixDigit(2) + ' ' +
           this.getHours().fixDigit(2) + ':' + this.getMinutes().fixDigit(2) + ':' + this.getSeconds().fixDigit(2);
}

// Off offf VBScript'in DateDiff'i var, JScript'in yeni oldu.. internetten buldum ama sonra bir şekilde bulduğum yeri kaybettim :(.
// Bu nedenle nereden aldığımı yazamıyorum. Elbette bulduğum yerde bu şekilde bir prototype değildi. Bu hale ben getirdim.
Date.prototype.dateDiff = function (h$) {
    if (arguments.length != 2) {
        throw new Error('Wrong number of arguments');
    }

    var interval = arguments[0];
    var dt1 = arguments[1];
    var dt2 = this;

    var diffMS = dt2.valueOf() - dt1.valueOf();
    var diff = new Date(diffMS);

    // calc various diffs
    var years  = dt2.getUTCFullYear() - dt1.getUTCFullYear();
    var months = dt2.getUTCMonth() - dt1.getUTCMonth() + (years==0 ? 0 : years*12);
    var quarters = parseInt(months/3);
   
    var milliseconds = diffMS;
    var seconds = parseInt(diffMS/1000);
    var minutes = parseInt(seconds/60);
    var hours = parseInt(minutes/60);
    var days = parseInt(hours/24);
    var weeks = parseInt(days/7);

    switch(interval.toLowerCase()){
        case "yyyy": return years;
        case "q": return quarters;
        case "m": return months;
        case "d": return days;
        case "w": return days;
        case "ww": return weeks;
        case "h": return hours;
        case "n": return minutes;
        case "s": return seconds;
        case "ms":return milliseconds;
        default: return "invalid interval: '" + interval + "'";
    }
}

function processFile(folder) {
    var files = new Enumerator(folder.Files);
    for (; !files.atEnd(); files.moveNext()) {
        var file = files.item();
        var fileName = file.Name;
        var fileCreateDt = new Date(file.DateCreated);
        var fileExtension = fileName.split('.').pop();
        var skipped = true;
        for (extension in fileTypes) {
            if (fileTypes[extension] == fileExtension) {
                skipped = false;
                break;
            }
        }

        if (!skipped) {
            if (toDay.dateDiff("d", fileCreateDt)>= expiryDay) {
                try {
                    file.Delete();
                    print('Deleted :' + fileName + ' - ' + fileCreateDt.format());
                } catch (e) {
                    print('Error At ' + fileName + ':' + e.name + ' - ' + e.message);
                }
            } else {
                print('Skipped (Day) :' + fileName + ' - ' + fileCreateDt.format());
            }
        } else {
            print('Skipped (Extension) :' + fileName + ' - ' + fileCreateDt.format());
        }
    }
}

function processFolder(folder) {
    processFile(folder);
    var subFolders = new Enumerator(folder.SubFolders);
    for (; !subFolders.atEnd(); subFolders.moveNext()) {
        processFolder(subFolders.item());
    }
}

function main() {
    var fso = new ActiveXObject('Scripting.FileSystemObject');
    fileTypes = sourceFileTypes.split(',');

    try {
        if (fso.DriveExists(sourceDriveName) == false || fso.FolderExists(sourceFolderName) == false) {
            throw new Error('Folder or Drive does not exist.!!');
        }

        processFolder(fso.GetFolder(sourceFolderName));
    } catch (e) {
        if (typeof(e) == 'string') {
            print(e);
        } else {
            print(e.name + ' - ' + e.message);
        }
    }
   
    var toDayStr = toDay.getFullYear().toString() + (toDay.getMonth() + 1).fixDigit(2) + toDay.getDate().fixDigit(2);
    toDayStr += toDay.getHours().fixDigit(2) + toDay.getMinutes().fixDigit(2) + toDay.getSeconds().fixDigit(2);
    var logFile = null;
    try {
        logFile = fso.CreateTextFile(sourceFolderName + '\\' + toDayStr + '_' + logFileName, OpenFileForWriting, true);
        logFile.Write(logStr);
    } catch (e) {
        if (logFile != null) {
            logFile.Close();
        }
        if (typeof(e) == 'string') {
            print(e);
        } else {
            print(e.name + ' - ' + e.message);
        }
        logFile = fso.CreateTextFile('C:\\' + toDayStr + '_' + logFileName, OpenFileForWriting, true);
        logFile.Write(logStr);
    } finally {
        if (logFile != null) {
            logFile.Close();
        }
    }

    return 0;
}

main();

Yaklaşık 160 satır, programın kendisi 130 satır civarında, recursive bir fonksiyona sahip, kütüphane desteği az olduğu için (neyseki prototype tabanlı) Date sınıfına dateDiff ve format methodları ile Number sınıfına fixDigit methodu eklenmiştir. Tamaniyle defansif bir yazım tekniği uygulanmıştır. prototype bazlı olduğu için dateDiff, format ve fixDigit methodları tekrar rahatlıkla kullanılabilir, hatta sizlerde kullabilirsiniz.

Sonuç olarak; Ruby açık ara önde, 2. sırada bence JScript geliyor(Hata yakalama işi önemli bir kayram), en son VBScript.

Belki gelecek yazılarda Java, C# ve Python versiyonlarını yazabilirim.. belli olmaz :)

Kolay gelsin...

Bağlı olduğu kategoriler; Bilgisayar, Ruby (Ruby on Rails)

Yaz aklındakini, çekinme :)

Lütfen: Yorumunuzun değerlendirilmesi ve yayınlanması uzun sürebilir. Yorumunuzu tekrar göndermeniz için bir neden yoktur, inanın tüm yorumları yayınlıyorum. Bazıları hariç :).