
Swego czasu postanowiłem napisać prostą aplikację to szybkiego tworzenia kopii zapasowych plików, i umieszczania ich na moim serwerze. Program ten stworzyłem w Adobe AIR plus zaplecze w PHP. W tym tutorialu opiszę proces powstawania tej aplikacji.
Tutorial Adobe AIR – tworzenie aplikacji
Przeczytamy tutaj m.in. o Drag&Drop, komunikacji z serwerem i obsłudze plików w Adobe AIR.
Kod źródłowy jako projekt Aptany oraz plik wynikowy .air do pobrania tutaj.
Podstawowe wytyczne dla aplikacji
Projekt przyjął następujące wytyczne:
– aplikacja do tworzenia kopii zapasowych plików,
– ładne, niewielkie okno, które możemy przeciągać,
– pliki wybieramy metodą drag&drop,
– jednorazowo możemy wysłać określoną liczbę plików,
– przycisk ‚upload’, po kliknięciu wysyła wybrane pliki na serwer,
– po stronie serwera pliki odbierane są przez skrypt PHP, który automatycznie zarządza nazwami,
– po wysłaniu plików program wyczyści i przygotuje bufory na nowy pakiet plików.
Zaczynamy tworzenie projektu Adobe AIR
Tworzymy nowy projekt Adobe AIR w Aptanie. Istotne są tutaj ustawienia okna, ponieważ chcemy sami wystylizować je w CSS. Dlatego w Window Options ustawiamy:
– w Window Style wybieramy Custom Chrome (transparent)
– szerokości i wysokości okna na 550 x 350
W sekcji Select Ajax Libraries natomiast, wybieramy bibliotekę jQuery.
Dodajemy do projektu następujące pliki:
– css/style.css
– icons/ajax_s.gif
– lib/myapp.js
– processor.php
Struktura projektu wygląda teraz następująco:
Otwieramy plik AirBackup.html i w sekcji head dodajemy pliki:
– lib/myapp.js
– css/style.css
Powinniśmy dodać także znacznik meta określający kodowanie, takie samo jak mamy ustawione w edytorze Aptany. Ja sam zawsze i wszędzie używam utf-8, zwłaszcza że często pracuję nad aplikacjami, które są dostępne w wielu językach, a kodowanie utf-8 zwyczajnie upraszcza sprawę.
Następnie w sekcji body tworzymy interfejs naszej aplikacji:
<body> <div id="my-window"> <div id="top-bar"> <span id="app-label"> AirBackup 1.1 by javascript-html5-tutorial.pl </span> <a href="#" id="app-close">X</a> </div> <div id="drop-area" ondragenter="handleDragEnter(event);" ondragover="handleDragOver(event);" ondrop="handleDrop(event);"> Proszę przeciągnąć pliki na ten obszar </div> <div id="files"> <ul></ul> </div> <span onclick="startUpload()" id="control-area"> <img src="icons/AIRApp_32.png" style="vertical-align: middle;" /> Wyślij </span> <div id="progress_pointer" style="display: none;"> Przetwarzanie...<img src="icons/ajax_s.gif" /> </div> <div class="clear"></div> </div> </body>
Element o id=”top-bar” stanowi pasek tytułowy okna, drop-area to obszar, na który będziemy upuszczać pliki, files będzie listą plików, mamy też element uruchamiania wysyłania oraz ukryty tzw. indicator, spotykany często w aplikacjach korzystających z AJAX.
Zwróćmy uwagę na zdarzenia z drop-area:
ondragenter="handleDragEnter(event);" ondragover="handleDragOver(event);" ondrop="handleDrop(event);"
Przypisane są do nich funkcje obsługi. Ich definicje znajdą się w pliku lib/myapp.js. Prostota implementacji Drag&Drop w Adobe AIR jest wręcz niewiarygodna, o czym za chwilę się przekonamy.
Aby nasze okno poprawnie się wyświetlało i było przyjazne dla użytkownika, potrzebujemy zdefiniować style CSS. Ja przygotowałem definicje jak poniżej.
Plik css/style.css:
/* * Definicje wyglądu aplikacji AirBackup 1.0 * (c) javascript-html5-tutorial.pl */ .clear { clear: both; } #my-window { width: 540px; height: 340px; background-color: #fff; border: 1px outset #00d; overflow: hidden; -webkit-border-radius: 10px; } #app-label { float: left; padding: 4px; cursor: move; } #top-bar { height: 30px; border-bottom: 1px solid #00a; text-align: right; padding: 2px 8px 0 4px; cursor: move; } #top-bar a { font-weight: bold; color: #f00; text-decoration: none; line-height: 30px; } #files { list-style-type: square; float: left; width: 300px; height: 200px; overflow: auto; padding: 5px; font-size: 9px; } #drop-area { float: left; width: 500px; height: 90px; border: 1px solid #00d; background-color: #efe; margin: 10px; padding-top: 10px; padding-left: 10px; font-size: 8pt; } #control-area { cursor: pointer; float: right; padding: 10px } #info { padding: 8px 0 4px 8px; font-size: 9pt; }
Przejdźmy do sedna – plik myapp.js
Jest to skrypt JavaScript, z implementacją obsługi funkcji aplikacji.
Sekcja konfiguracyjna:
// adres docelowy const SERVER_URL = "http://www.directcode.eu/airbackup/processor.php"; // maksymalna ilość plików, jaką możemy wysłać jednorazowo const FILES_MAX_AMOUNT = 10;
Bardzo ważne jest, aby w stałej SERVER_URL ustawić poprawną ścieżkę do swojego skryptu na serwerze. Skrypt ten (przedstawię go za chwilę) odbiera i zapisuje wysłane przez nas pliki.
Realny skrypt podany w moich przykładach, to jest http://www.directcode.eu/airbackup/processor.php, ze względów bezpieczeństwa ma usunięty kod odpowiedzialny za zapis plików na serwerze.
Dalej dodajemy trochę kodu bazującego na jQuery, obsługującego nasze okno:
$(document).ready(function() { // przeciąganie okna chwytając za element top-bar $("#top-bar").mousedown(function() { window.nativeWindow.startMove(); }); // zamykanie aplikacji $("#app-close").click(function() { window.nativeWindow.close(); return false; }); });
Następnie definiujemy tablice globalne:
// nazwy plików var backupFileNames = new Array(); // tablica obiektów typu File var backupFiles = new Array();
oraz funkcje obsługi zdarzeń
function handleDragEnter(event) { event.preventDefault(); } function handleDragOver(event) { event.preventDefault(); } function handleDrop(event) { // kontrola ilości plików do jednorazowego wysłania if (backupFiles.length >= FILES_MAX_AMOUNT) { alert('Wyślij bieżące pliki. Limit 1 pakietu wynosi ' + FILES_MAX_AMOUNT); return; } // budujemy listę plików var fileList = event.dataTransfer.getData(air. ClipboardFormats.FILE_LIST_FORMAT); for (var i in fileList) { if ($.inArray(fileList[i].name, backupFileNames) == -1) { backupFileNames.push(fileList[i].name); backupFiles.push(fileList[i]); $("#files ul").append('<li>' + fileList[i].name + '</li>'); } } showFilesInfo(); }
Implementujemy funkcje pomocnicze:
// wyświetlanie informacji o plikach function showFilesInfo() { if ($("#info").length == 0) { $("#top-bar").after('<div id="info"></div>'); } else { $("#info").empty(); } var _size = getTotalFileSize() / 1024; // kB // formatuj do 2 miejsc po przecinku var _formatted_size = (Math.floor(_size * 100)) / 100; $("#info") .append("Liczba plików: " + backupFiles.length + "<br />") .append("Sumaryczny rozmiar plików: " + _formatted_size + " kB"); } // obliczanie rozmiaru dodanych plików function getTotalFileSize() { var sum = 0; for (var i in backupFiles) { sum += backupFiles[i].size; } return sum; } // czyszczenie function doCleanups() { $("#progress_pointer").hide(); backupFileNames = []; backupFiles = []; $("#files").empty(); $("#info").empty(); }
I gwóźdź programu:
// funkcja wykonująca upload plików na serwer function startUpload() { if (backupFiles.length == 0) { alert('Brak plików do wysłania na serwer.'); return; } var boundary = '--AaB03x'; var request = null; var file = null; // nowe żądanie request = new air.URLRequest(SERVER_URL); request.useCache = false; request.contentType = 'multipart/form-data, boundary=' + boundary; request.method = 'POST'; var buffer = new air.ByteArray(); // pokaż indicator $("#progress_pointer").show(); // przetwórz listę plików for (x in backupFiles) { file = new air.File(backupFiles[x].nativePath); fileStream = new air.FileStream(); fileStream.open(file, air.FileMode.READ); fileContents = new air.ByteArray(); fileStream.readBytes(fileContents, 0, file.size); fileStream.close(); buffer.writeUTFBytes("--" + boundary + "\r\n"); buffer.writeUTFBytes("content-disposition: form-data; name=\"Filedata\"; filename=\"" + file.name + "\"\r\n"); buffer.writeUTFBytes("Content-Transfer-Encoding: binary\r\n"); buffer.writeUTFBytes("Content-Length: " + file.size + "\r\n"); buffer.writeUTFBytes("Content-Type: application/octet-stream\r\n"); buffer.writeUTFBytes("\r\n"); buffer.writeBytes(fileContents, 0, fileContents.length); buffer.writeUTFBytes("\r\n--" + boundary + "--\r\n"); request.data = buffer; var loader = new air.URLLoader(); loader.addEventListener(air.ProgressEvent.PROGRESS, function(e) { // alert('progress: ' + e); }); loader.addEventListener(air.IOErrorEvent.IO_ERROR, function(e) { alert('error: ' + e.text); }); loader.addEventListener(air.Event.COMPLETE, function(e) { if (loader.data) { alert(loader.data); doCleanups(); } }); loader.load(request); } }
To wszystko. Możemy uruchomić i przetestować naszą aplikację.
Ja wybrałem do wysłania te same pliki dwukrotnie, więc na serwerze mam już zabezpieczone kopie moich plików:
A oto skrypt PHP działający po stronie serwera
Właściwie to najprostsza wersja skryptu. Zajmuje się obsługą przesłanych plików, a dodatkowo zawiera kod, który dodaje znacznik czasu do nazwy pliku, jeśli takowy już istnieje, aby go nie nadpisać:
// plik processor.php // nalezy upewnic sie ze ten folder istnieje i skrypt // ma prawa zapisu do niego $upload_dir = "./uploads/"; $err_state = true; $temp_file = $_FILES['Filedata']['tmp_name']; $file_name = $_FILES['Filedata']['name']; $file_size = $_FILES['Filedata']['size']; // jesli istnieje dodaj UNIX Timestamp do nazwy jako prefix $tmp_file_name = ""; if (file_exists($upload_dir . $file_name)) { $tmp_file_name = time() . '_' . $file_name; if (move_uploaded_file($temp_file, $upload_dir . $tmp_file_name)) { $err_state = false; // ... } } else { if (move_uploaded_file($temp_file, $upload_dir . $file_name)) { $err_state = false; // ... } } if (!$err_state) { // upload przebiegl pomyslnie echo "Sukces: przechwycono i zabezpieczono pliki."; } else { echo "Error: upload nie powiodl sie."; }
Jeśli chcemy nadpisywać pliki, aby mieć na serwerze tylko najnowsze ich wersje, zmieniamy następujący kod:
$tmp_file_name = ""; if (file_exists($upload_dir . $file_name)) { $tmp_file_name = time() . '_' . $file_name; if (move_uploaded_file($temp_file, $upload_dir . $tmp_file_name)) { $err_state = false; // ... } } else { if (move_uploaded_file($temp_file, $upload_dir . $file_name)) { $err_state = false; // ... } }
pisząc po prostu:
if (move_uploaded_file($temp_file, $upload_dir . $file_name)) { $err_state = false; // ... }
I to wszystko – wytyczne projektu zostały spełnione.
Podsumowanie
Tak oto nauczyliśmy się kilku ciekawych zagadnień dotyczących Adobe AIR oraz mamy całkiem przydatną aplikację. Sam jej używam, gdyż wykonywanie kopii ważnych plików na serwer jest dla mnie bardzo istotne. A teraz mogę to zrobić jeszcze szybciej.
Mam nadzieję że Czytelnik jeszcze bardziej zainteresował się technologią Adobe AIR.
Źródła do pobrania tutaj.