Σκεψεις για την “Τελευταια Μπλοφα”

Είμαι σε διακοπές, και όπου βρεθώ μια αγαπημένη συνήθεια είναι η επίσκεψη στο τοπικό βιβλιοπωλείο. Πάνω πάνω στα ευπώλητα ήταν το “Η τελευταία μπλόφα” της Ε. Βαρβιτσιώτη και Β. Δενδρινού. Το ειχα δει και τρέντινγκ στα σόσιαλ, ε το πήρα.

Το ρούφηξα σε λιγότερο από 24 ώρες. Και δεν είναι τόσο καλό όσο λένε, είναι ακόμη καλύτερο.

Παρότι ήξερα την ιστορία, σε ικανοποιητικό βαθμό, ως οικονομική και πολιτική, το βιβλίο την εξιστορεί από την προσωπική ματιά των πρωταγωνιστών. Αποτυπώνονται στιγμές που, για προφανείς λόγους, δεν είδαν το φως της δημοσιότητας όπως σύμβουλοι να απελπίζονται, πρωθυπουργοί να κοιμούνται σε καναπέδες, ο Ρέντσι να βάζει τις φωνές στη Μέρκελ και ο Ολάντ να πηδάει μπαλκόνια (!)

Αλλά δεν είναι κουτσομπολίστικο βιβλίο. Ίσα ίσα που δίνει την πίεση που είχαν όλοι -ή σχεδόν όλοι- να βρουν μια λύση σε μια κατάσταση που χάρη στην καταστροφική ανωριμότητα της τότε ελληνικής κυβέρνησης πήγε από το κακό στο χειρότερο σε χρόνο μηδέν.

Καταγραφω 2-3 σκέψεις, ιδέες και απορίες που έχω, τόσο για την ιστορία όσο και για το βιβλίο:

Η πρώτη και σίγουρα η σημαντικότερη: φαίνεται στο βιβλίο, αλλα ήταν και προφανές σε όσους παρακολουθήσαμε(*) αυτή την ιστορία, από τις αρχές μέχρι το φθινόπωρο του 2015, ότι η συμπεριφορά των εταίρων της Ελλάδας (χώρες ΕΕ, ΕΚΤ, ΔΝΤ κλπ.) ήταν πολύ ελαστικότερη προς την νέα τότε κυβέρνηση (ΣΥΡΙΖΑ-ΑΝΕΛ) παρά προς τις προηγούμενες (ΠΑΣΟΚ & ΝΔ-ΠΑΣΟΚ-ΔΗΜΑΡ). Κι αυτό όχι μόνο στην αρχή αλλά ακόμα και αργότερα, όταν είχε ήδη φανεί καθαρά ότι οι περισσότεροι άνθρωποι της νέας κυβέρνησης ήταν ιδεοληπτικοί, ανίκανοι για οποιαδήποτε σοβαρή εργασία τέτοιου μεγέθους και πολυπλοκότητας.

Γιατί;

Το βιβλίο δεν δίνει κάποια απάντηση. Σε κάποια σημεία μόνο διαφαίνεται, αν το ερμηνεύω σωστά, ότι με τις προηγούμενες κυβερνήσεις οι εταίροι είχαν αφενός μπουχτίσει με την απροθυμία τους να κάνουν τις επώδυνες αλλά αναγκαίες μεταρρυθμίσεις και αφετέρου τις θεωρούσαν -σωστά- υπεύθυνες για το χάλι της χώρας. Αν ισχύει αυτό, είναι στην ουσία παρόμοια λογική με το “τους είδαμε τους παλιούς, ας δοκιμάσουμε κάτι καινούριο” που έλεγαν αρκετοί πολίτες πριν τις εκλογές του 2015(**).

Η δεύτερη είναι οτι πρέπει να θυμόμαστε, όσοι δεν ασχολούμαστε ενεργά με την πολιτική, ότι η πολιτική κρίνεται από τα αποτελέσματα, όχι από τα λόγια. Είναι χαρακτηριστική η εικόνα της Μέρκελ που επισκέπτεται την Ελλάδα προ των εκλογών του 2015, βλέπει τα συνθήματα (“go back” κλπ) και, μετά τις εκλογές, δεν έχει κανένα πρόβλημα να συνεργαστεί με τους ίδιους που την καθύβριζαν.

Και η τρίτη, για το βιβλίο: θα ήταν χρήσιμο, σε κάποια επανέκδοση ίσως, να μπει ένα timeline, μια χρονική γραμμή που να δείχνει την αλληλουχία των γεγονότων. Ίσως ακόμα και δυο: μια από το 2009 ως το τέλος του 2015, που να δείχνει τα μείζονα γεγονότα (εκλογές 2009, Καστελόριζο, μνημόνιο 1, κυβέρνηση Παπαδήμου κλπ) και μια να κάνει ζουμ από την αρχή του 2015 ως το φθινόπωρο, με τα πάμπολλα Eurogroup, τα capital controls κλπ.

(*) την ελληνική κρίση την έζησα “άμεσα”, ζούσα και εργαζόμουν δηλαδή στην Ελλάδα, μέχρι την άνοιξη του 2012. Ήδη στα τέλη του 2011 είχα αποφασίσει ότι το ρίσκο για μένα, είτε λόγω grexit είτε πολύ απλά ότι μπορεί να έμενα άνεργος λόγω ύφεσης, ήταν πολύ υψηλό. Έτσι τον Φεβρουάριο του 2012 ξεκίνησα να ψάχνω για δουλειά στο εξωτερικό, και τέλη Απρίλη έμπαινα σε μια γκαρσονιέρα της Ζυρίχης. Από το φθινόπωρο 2012 και πέρα, παρότι φυσικά παρακολουθούσα τα πάντα με λεπτομέρεια, οι εξελίξεις δεν είχαν πάνω μου τις άμεσες συνέπειες που είχαν για τους φίλους μου -όσους δεν είχαν ήδη βγαλει εισητήριο χωρίς επιστροφή.

(**) το κυριότερο επιχείρημα όλων όσων τους έλεγα ότι οι ισχυρισμοί ΣΥΡΙΖΑ ήταν ανεδαφικοί (“θα καταργήσω τα μνημόνια μ’ένα νόμο κι ένα άρθρο” και άλλα ανεκδιήγητα), παρότι το καταλάβαιναν και το δεχόταν, ήταν “έστω και τα μισά από αυτά που υπόσχεται να κάνει, πάλι καλά θα είναι”. Αυτό… όπως είδαμε όλοι, δεν πήγε και πολύ καλά.

Powershell – file system operations within a transaction

Anyone who’s ever developed anything connected to a database knows about transactions. Using a transaction we can group data operations so that they happen “all or nothing”, i.e. either all of them succeed or no one does. One example is a transfer from one bank account to another: the complete transaction requires subtracting the amount to be transferred from one account and adding that same amount to the other. We wouldn’t want one to happen without the other, would we?

(yes, I know that’s not a complete description of what transactions are or do; that’s not my purpose here)

But what happens when we need the same from our filesystem?

Let’s take this scenario (which is a simplified version of a true story): we are receiving CSV files from solar panels (via SFTP) and we want to do some preprocessing and then store the data to a database. When processing them we have to generate a lot of intermediate files. After that, we want to move them to a different dir. But if something happens, say if the database is down, we want the whole thing to be cancelled so that, when we retry, we can start over.

Obviously a simple solution would be as follows:

try {
  # do a lot of hard work
  # store the data in the db
  # clean up intermediate files
  # move the CSV file to an "archive" dir
}
catch {
  # clean up intermediate files, potentially clean up any db records etc
}

That’s… well it can work but it’s not great. For example, the cleanup process itself -inside the catch block- might fail.

A much better process would be like that:

try {   
  # start a transaction
    # do a lot of hard work   
    # store the data in the db   
    # clean up intermediate files   
    # move the CSV file to an "archive" dir 
  # commit the transaction
} 
catch {   
  # rollback everything: files, db changes, the whole thing
}

That’s much cleaner! But is it possible to group db changes and filesystem changes (file create, move, delete & append, dir create & delete etc) all in one transaction?

Unix geeks are allowed to feel smug here: some flavors like HP-UX (though not Linux as far as I know) have this baked in from the get go. Your code doesn’t have to do anything special; the OS takes care of this on user request.

But as a matter of fact yes, it is also available on Windows, and it has been for some time now. The requirement is that you’re working on a file system that supports this, like NTFS.

But there’s a problem for the average .NET/Powershell coder: the standard methods, the ones inside System.IO, do not support any of this. So you have to go on a lower level, using the Windows API. Which for .NET coders, there’s no other way to put this, sucks. That’s also the reason why the Powershell implementation of file transactions (e.g. New-Item -ItemType File -UseTransaction) doesn’t work -it relies on System.IO.

I’m pretty sure that this is what crossed the minds of the developers that wrote AlphaFS which is just wonderful. It’s exactly what you’d expect but never got from Microsoft: a .NET implementation of most of System.IO classes that support NTFS’s advanced features that are not available in, say, ye olde FAT32. Chief among them is support for file system transactions.

So the example below shows how to do exactly that. I tried to keep it as simple as possible to highlight the interesting bits, but of course a real world example would be much more complicated, e.g. there would be a combined file system and database transaction, which would commit (or rollback) everything at the same time.

Note that there’s no need for an explicit rollback. As soon as the transaction scope object is disposed without calling Complete(), all changes are rolled back.

#
# Source: DotJim blog (http://dandraka.com)
# Jim Andrakakis, July 2019
#
# Prerequisite: 
#   1) internet connectivity
#   2) nuget command line must be installed 
#      (see https://www.nuget.org/downloads).
# If nuget is not in %path%, you need to change 
#   the installation (see below) to call it with 
#   its full path.
 
# Stop on error
$ErrorActionPreference = "Stop"

if ($psISE)
{
    $binPath = Split-Path -Path $PSISE.CurrentFile.FullPath        
}
else
{
    $binPath = $PSScriptRoot
}
$alphaFSver = "2.2.6"
$libPath = "$binPath\AlphaFS.$alphaFSver\lib\net40\AlphaFS.dll"
$basePath = "$binPath\..\alphatest"

# ====== installation ======
if (-not [System.IO.File]::Exists($libPath)) {
    Out-File -FilePath "$binPath\packages.config" `
        -Force `
        -Encoding utf8 `
        -InputObject ("<?xml version=`"1.0`" encoding=`"utf-8`"?><packages>" + `
          "<package id=`"AlphaFS`" version=`"$alphaFSver`" targetFramework=`"net46`" />" + `
          "</packages>")
    cd $binPath
    & nuget.exe restore -PackagesDirectory "$binPath"
}
# ==========================
 
# Make sure the path matches the version from step 2
Import-Module -Name $libPath
 
if (-not (Test-Path $basePath)) {
    New-Item -ItemType Directory -Path $basePath
}
 
# Check if the filesystem we're writing to supports transactions.
# On a FAT32 drive you're out of luck.
$driveRoot = [System.IO.Path]::GetPathRoot($basePath)
$driveInfo = [Alphaleonis.Win32.Filesystem.DriveInfo]($driveRoot)
if (-not $driveInfo.VolumeInfo.SupportsTransactions) {
    Write-Error ("Your $driveRoot volume $($driveInfo.DosDeviceName) " + `
      "[$($driveInfo.VolumeLabel)] does not support transactions, exiting")
}
 
# That's some example data to play with.
# In reality you'll probably get data from a DB, a REST service etc.
$list = @{1="Jim"; 2="Stef"; 3="Elena"; 4="Eva"}
 
try {
    # Transaction starts here
    $transactionScope = [System.Transactions.TransactionScope]::new([System.Transactions.TransactionScopeOption]::RequiresNew)
    $fileTransaction = [Alphaleonis.Win32.Filesystem.KernelTransaction]::new([System.Transactions.Transaction]::Current)
 
    # Here we're doing random stuff with our files and dirs, 
    #   just to show how this works.
    # The important thing to remember is that for the transaction 
    #   to work correctly, ALL methods you use have to be -transacted.
    # I.e. you must not use AppendText() but AppendTextTransacted(), 
    #   not CreateDirectory() but CreateDirectoryTransacted() etc etc.
    $logfileStream = [Alphaleonis.Win32.Filesystem.File]::AppendTextTransacted($fileTransaction, "$basePath\list.txt")
    foreach($key in $list.Keys) {
        $value = $list.$key
        $filename = "$([guid]::NewGuid()).txt"
        $dir = "$basePath\$key"
 
        Write-Host "Processing item $key $value"
 
        if (-not [Alphaleonis.Win32.Filesystem.Directory]::ExistsTransacted($fileTransaction, $dir)) {
            [Alphaleonis.Win32.Filesystem.Directory]::CreateDirectoryTransacted($fileTransaction, $dir)
        }
        [Alphaleonis.Win32.Filesystem.File]::WriteAllTextTransacted($fileTransaction, "$basePath\$key\$filename", $value)        
        $logfileStream.WriteLine("$filename;$key;$value")
    }
    $logfileStream.Close()
     
    # to simulate an error and subsequent rollback:
    # Write-Error "Something not great, not terrible happened"
     
    # Commit transaction
    $transactionScope.Complete()
    Write-Host "Transaction committed, all modifications written to disk"
}
catch {
    Write-Host "An error occured and the transaction was rolled back: '$($_.Exception.Message)'"
    throw $_.Exception
}
finally {
    if ($null -ne $logfileStream -and $logfileStream -is [System.IDisposable]) {
        $logfileStream.Dispose()
        $logfileStream = $null
    }    
    if ($null -ne $transactionScope -and $transactionScope -is [System.IDisposable]) {
        $transactionScope.Dispose()
        $transactionScope = $null
    }    
}

Have fun coding!