Druga verzija programa

13.3 Druga verzija programa

Problem: Ijao.... Prvo i prvo, ne sviđa mi se način na koji se dodeljuje ime arhivi, a potom – izgleda da nam program ima jako velik bag! Kada mu navedem direktorijum koji želim da se bekapuje – on to ne radi! Arhiva ne sadrži niti jedan fajl koji se nalazi unutar tog zadatog direktorijuma! Ovo je, u najboljem slučaju – sranje od programa!
Odgovor: Prva verzija naše skripte radi. Ali, kao što si sam primetio, postoje i ograničenja. Upisivati fajl po fajl i nije baš neko zadovoljstvo, zar ne? Ali, ni mi nismo nemoćni – imamo naš mozak da nam priskoči u pomoć (ako ga jutros nisam zaboravio u kafani, zajedno sa šeširom i novinama). Uz pomoć našeg znanja, i informacija o problemu, mi možemo napraviti neke manje ili veće izmene unutar programa, kako bi on mogao da ispuni sve naše zahteve prilikom svakodnevne upotrebe. Ovo se naziva faza održavanja softvera.
Na koji problem želimo prvo da se skoncentrišemo? Sve nam je jedno, nigde ne žurimo – dokle god znamo šta želimo da dobijemo.
Prva izmena bi bila da naš program može da bekapuje cele direktorijume, a druga je bolji mehanizam za imenovanje arhiva.
Prvo što ćemo uraditi je – osiguranje. Ti ne želiš da sa svojim izmenama „naškodiš“ programu koji radi, zar ne? Šta treba da uradimo? Pa, najlakše je da sačuvamo onaj prethodni program pod drugim imenom, na primer beckap_v2.py. Dovoljno je da u svom editoru otvorimo prvu verziju naše skripte, i da kliknemo na save as i potom izaberemo gde će da se sačuva, i navedemo novo ime fajla. Ovim jednostavnim postupkom smo se osigurali da imamo verziju koja radi (kako tako, ali je ipak funkcionalna) i verziju na kojoj radimo, i koja, najverovatnije, pre završetka svih poslova, neće biti funkcionalna.
Drugi način osiguravanja protiv nas samih bi bio da sve funkcije koje neće biti menjane „gurnemo“ u module, pa da menjamo samo kodove u onom modulu koji je „kritičan“. Budući da je naš program, u ovom trenutku, prilično jednostavan, mnogo je lakše samo ga kopirati sa drugim imenom.
Koji problem od ova dva, koja nam „zagorčavaju život“ želimo da prvi rešavamo? Moj predlog je da prvo rešimo problem sa imenovanjem arhive. Ideja je da ime arhive bude trenutno vreme, ali da se arhiva skladišti unutar direktorijuma koji će imati ime koje je trenutni datum, i biće kreiran unutar direktorijuma koji korisnik odredi! Prva prednost ovakvog pristupa je u tome, da se arhive sada čuvaju sortirane, po hijerarhiji datuma i vremena nastanka, pa se zato možemo mnogo lakše snaći među njima, i možemo mnogo lakše da nađemo ono što nam treba! Druga prednost ovog pristupa je u tome što je sada ime arhive mnogo kraće. Treća prednost, koja proizilazi od sortiranja po direktorijumima, je da možemo da vodimo evidenciju po danima – jednostavno možemo da proverimo da li smo napravili rezervnu kopiju dana kada smo, na primer, vršili neke važne izmene nad fajlovima, jer će sad da direktorijum ima naziv koji je trenutni datum i biće kreiran samo u slučaju kada pravimo prvu rezervnu kopiju podataka toga dana.
Šta je problem, tj šta želimo? Očigledno nam je problem u funkciji  ime_arh(). Ona sada, sem svoje funkcije imenovanja arhive, mora da proveri da li postoji direktorijum sa današnjim datumom i ako taj direktorijum ne postoji, mora da ga kreira, pa, hajde da je izmenimo:
def ime_arh(destinacija): 
    '''Kreira direktorijum, koji će se nalaziti u destinaciji a nosi ime koje je 
    trenutni datum. Vraća string koji predstavlja naziv arhive.''' 
    datum = time.strftime('%Y%m%d') 
    vreme = time.strftime('%H%M%S') 
    direktorijum = '{}{}{}'.format(destinacija, os.sep, datum) 
    if not os.path.exists(direktorijum): 
        os.mkdir(direktorijum) 
        print('Kreirao sam novi direktorijum -->', direktorijum) 
    arhiva = '{0}{1}{2}.zip'.format(direktorijum, os.sep, vreme) 
    return arhiva
Veći deo ove funkcije više nije isti. Prvo smo kreirali stringove koji predstavljaju današnji datum i trenutno vreme, pomoću funkcije strftime() koja se nalazi unutar time modula. Zatim smo generisali ime direktorijuma, a zatim smo proverili da li direktorijum sa tim imenom već postoji pomoću funkcije os.path.exists(). Ako ne postoji, mi ga kreiramo pomoću funkcije os.mkdir().
Budući da smo izvršili malu izmenu, bio bi red da proverimo da li naša funkcija sada radi:
./beckap_v2.py 
Unesi imena fajlova koje želiš da bekapuješ 
Unesi reč "kraj" za kraj unosa. 
Veran Bekaper v1.1 >>> /home/lenj/Desktop/Novi rad/Problemator.pdf 
Unesi reč "kraj" za kraj unosa. 
Veran Bekaper v1.1 >>> kraj 
Gde želiš da sačuvaš arhivu? 
Veran Bekaper v1.1 >>> /home/lenj/Desktop 
Kreirao sam novi direktorijum --> /home/lenj/Desktop/20131029 
Radim.... 
Kraj! Pritisni Enter za izlaz! 
Opa! Ova funkcija dobro radi. Doduše, prompt nam ne prikazuje trenutnu verziju programa ispravno, ali to se da lako rešiti. Nama je osto jedan mnogo teži zadatak....
Zašto naša skripta ne bekapuje sadržaje direktorijuma, već samo pojedinačne fajlove? Pa, odgovor je – zbog zipfile objekta. U njega prosleđujemo i upisujemo samo jedan fajl u jednim trenu, pa, ukoliko mu umesto fajla prosledimo direktorijum, on u našu arhivu samo stavi – putanju do direktorijuma! Ako se malo razmisli, ovakvo ponašanje je sasvim normalno. Zamislite sebe da ste u nepoznatom gradu punom nekih maskiranih ljudi koji ne deluju prijateljski. Želite da pobegnete odatle, i jadan od tih ljudi vam pokaže na neka vrata govoreći „tamo je izlaz“. Vi ne znate šta je iza vrata, i, ako ste dovoljno pametni, nećete ni prolaziti kroz njih, ali ćete ih imati u glavi kao krajnju soluciju („Možda je onaj fini čovek što nosi onako lepo dizajniranu masku preko lica, nož u zubima i kalašnjikov pod miškom, ipak u pravu?“). Tako isto i zipfile – vi ste mu pokazali na direktorijum, ali on ne zna, niti želi da zna šta je u njemu, jer smatra da, ako si ti hteo da mu kažeš koji fajl ti treba, da bi mu to i rekao.
Sada se mi nalazimo u problemu. Da li da napišemo funkciju koja će proveravati da li je korisnikov unos fajl ili folder, pa ako je folder, moraće rekurzivno sebe da poziva sve dok ne „prodre“ do najdubljeg fajla koji se nalazi duboko ugnježden u strukturi prosleđenog foldera... Teško je za objasniti, nije teško tako nešto ni napraviti, ali, čini mi se da bi uzimala previše vremena mom procesoru. Imam mnogo bolju ideju, i mnogo elegantnije rešenje, koje čak, neću ni staviti u zasebnu funkciju, već ću direktno da izmenim funkciju bekap()! Možda ćeš naći gomilu mana za moje rešenje, ti možeš da ga kreiraš u zasebnoj funkciji, kako bi rasteretio ovu, ali moja ideja je ovakva:
def bekap(izvor, destinacija): 
    '''Vrši bekap fajlova koji se nalaze u listi izvor u destinaciju.''' 
    arhiva = zipfile.ZipFile(destinacija, mode = 'w', allowZip64 = True) 
    for fajl in izvor: 
        if os.path.isfile(fajl): 
            arhiva.write(fajl, arcname = os.path.abspath(fajl), compress_type = zipfile.ZIP_DEFLATED) 
        elif os.path.isdir(fajl): 
            # Idemo sa os.walk u svaki fajl direktorijuma 
            for direktorijum , subdirektorijum, mete in os.walk(fajl): 
                for deo in mete: 
                    svaki_fajl = (os.path.join(direktorijum, deo)) 
                    arhiva.write(svaki_fajl, os.path.abspath(svaki_fajl), zipfile.ZIP_DEFLATED) 
        else: 
            print('Ne mogu da nađem -->', fajl) 
    arhiva.close()
Vidiš li šta sam uradio? Za svaki fajl koji se nalazi u listi, sam izvršio proveru. Ukoliko je fajl stvarno fajl – momentalno ga upisujem u arhivu. Ukoliko je fajl u stvari direktorijum, koristim funkciju walk() koja se nalazi unutar os modula, a koja nam vraća tuple od tri stvari – prva je ime direktorijuma, druga je lista sa imenima subdirektorijuma, a treća je lista svih fajlova koji se nalaze u „drvetu“ prosleđenog direktorijuma. U suštini, nas zanima samo prva i treća stavka, jer u listi svih fajlova oni su navedeni sa svojim imenima, a ne putanjom do njih! Zato smo za svaki fajl morali da radimo os.path.join(direktorijum, deo) koji će kreirati pravilnu putanju do naše mete. Nakon toga, tako dobijenu putanju prosleđujemo našem zipfile objektu kako bi se bekapovao.
Naš program bi na kraju trebao da izgleda ovako:
#!/usr/bin/env python3 

'''Skripta koja radi bekap fajlova.''' 

import time 
import os, zipfile 

__version__ = '2.0' 

def pita(pitanje, kraj = None): 
    '''Pita korisnika pitanja, ukoliko nije naznačena reč za kraj 
    pita samo jedno pitanje, vraća listu odgovora.''' 
    odgovori = [] 
    prompt = 'Veran Bekaper v2.0 >>> ' 
    if not kraj: 
        print(pitanje) 
        odgovor = input(prompt) 
        odgovori.append(odgovor) 
    else: 
        print(pitanje) 
        while True: 
            print('Unesi reč "{}" za kraj unosa.'.format(kraj)) 
            odgovor = input(prompt) 
            if odgovor == kraj: 
                break 
            odgovori.append(odgovor) 
    return odgovori 

def ime_arh(destinacija): 
    '''Kreira direktorijum, koji će se nalaziti u destinaciji a nosi ime koje je 
    trenutni datum. Vraća string koji predstavlja naziv arhive.''' 
    datum = time.strftime('%Y%m%d') 
    vreme = time.strftime('%H%M%S') 
    direktorijum = '{}{}{}'.format(destinacija, os.sep, datum) 
    if not os.path.exists(direktorijum): 
        os.mkdir(direktorijum) 
        print('Kreirao sam novi direktorijum -->', direktorijum) 
    arhiva = '{0}{1}{2}.zip'.format(direktorijum, os.sep, vreme) 
    return arhiva 
            

def bekap(izvor, destinacija): 
    '''Vrši bekap fajlova koji se nalaze u listi izvor u destinaciju.''' 
    arhiva = zipfile.ZipFile(destinacija, mode = 'w', allowZip64 = True) 
    for fajl in izvor: 
        if os.path.isfile(fajl): 
            arhiva.write(fajl, arcname = os.path.abspath(fajl), compress_type = zipfile.ZIP_DEFLATED) 
        elif os.path.isdir(fajl): 
            # Idemo sa os.walk u svaki fajl direktorijuma 
            for direktorijum , subdirektorijum, mete in os.walk(fajl): 
                for deo in mete: 
                    svaki_fajl = (os.path.join(direktorijum, deo)) 
                    arhiva.write(svaki_fajl, arcname = os.path.abspath(svaki_fajl), compress_type = zipfile.ZIP_DEFLATED) 
        else: 
            print('Ne mogu da nađem -->', fajl) 
    arhiva.close() 

def glavni(): 
    '''Glavni deo programa.''' 
    izvor = pita('Unesi imena fajlova koje želiš da bekapuješ', 'kraj') 
    destinacija = pita('Gde želiš da sačuvaš arhivu?') 
    # vraćena nam je lista treba nam string 
    destinacija = destinacija[0] 
    ime = ime_arh(destinacija) 
    print('Radim....') 
    bekap(izvor, ime) 
    input('Kraj! Pritisni Enter za izlaz!') 


if __name__ == '__main__': 
    glavni()
Ali, da li sada radi?
./beckap_v2.py 
Unesi imena fajlova koje želiš da bekapuješ 
Unesi reč "kraj" za kraj unosa. 
Veran Bekaper v2.0 >>> /home/lenj/Desktop/Novi rad 
Unesi reč "kraj" za kraj unosa. 
Veran Bekaper v2.0 >>> kraj 
Gde želiš da sačuvaš arhivu? 
Veran Bekaper v2.0 >>> /home/lenj/Desktop 
Kreirao sam novi direktorijum --> /home/lenj/Desktop/20131029 
Radim.... 
Kraj! Pritisni Enter za izlaz! 
Uh.... Izgleda da je ovo održavanje programa baš teško... Pošto sam ja umoran, ostavljam tebi jednu malu izmenu u ovoj skripti: ime foldera u kojem se nalazi naš bekap je 20131029. To je glupo ime, mislim da bi bilo bolje „Bekap-29-10-2013“. Dakle? Šta čekaš... Ovo je minorna izmena....

13.2 Rešavanje Indeks 13.4 Treća
verzija programa

Primjedbe

Popularno

Kako televizija štetno utiče na Vaše zdravlje finansije?

Da li je TV kviz Slagalica "prevara"?

Tu je novi Qt creator 4.4.0!

Kada prijatelji odlaze

Microsoft objavio - nova verzija Windowsa stiže...

Opet su se pojavili lažni kuponi brenda "IDEA" na društvenim mrežama

Najgore obrade velikih rock hitova

Sitnije promene na blogu...

Poslednji pozdrav izgubljenom vremenu

Prevelika očekivanja