blog

Mi a baj Western Digital Green Power merevlemezeivel?

Az, hogy túl zöld. Mondhatni sötétzöld. De ne legyünk ilyen durvák, nézzük a tényeket, spekulációkat.

Az SPCR fórumára bevágott WD Support által írt levelek és SMART adatok szerint a WD GP winyók 8 másodperc inaktivitás után parkolják a fejet. A másfél éve létező SCPR fórum után az elmúlt egy évben valamikor felkerült a hivatalos Western Digital tudástárba is egy bejegyzés a problémáról (The S.M.A.R.T Attribute 193 Load/Unload counter keeps increasing on a SATA 2 hard drive).

A funkció előnye, hogy kevesebb áramot fogyaszt a merevlemez, ellenben növekszik a SMART Load_Cycle_Count (LCC) számláló értéke. A winyó adatlapja szerint ezekből a parkolásokból legalább 300 ezret ki kellene bírnia. Ha azonban pechesek vagyunk, és mondjuk pont 10 másodpercenként nyúlunk a lemezhez (vagy megteszi egy programunk 10 másodpercenként), akkor ezt a 300 ezres számot viszonylag hamar elérhetjük (kb. 35 nap folyamatos üzem esetén).

Persze ez nem jelenti azt, hogy a 300 001-edik parkolás már biztosan nem fog sikerülni, és utána a winyón tárolt adatok sosem lesznek elérhetőek, de akkor is idegesítő. Főleg, ha a parkolás hangját is halljuk. A fentebb említett fórumon egyébként van néhány felhasználó, akiknek több milliónál jár ez a számláló és a winyó hibátlanul működik.

De nézzük, hogy miért nem túl fair a helyzet:

  • A winyó adatlapján, weblapján, és általában úgy sehol sincs feltüntetve, hogy rendelkezik ezzel a funkcióval. A felhasználók egy része nem azért vett WD GP winyót, mert annyira zöld, hanem mert csak 5400-at fordul percenként, ami jóval csendesebb működést és kevesebb hőt eredményez, mint a szokásos 7200-as változatok. Tényleg érdekel valakit az a pár Watt? Tegyük hozzá, hogy a parkolás sem teljesen zajtalan művelet.
  • A Raid Edition 2-es Green Power winyókhoz a gyártó kiadott egy firmware frissítést és egy programot, amivel ezt a funkciót testre lehet szabni (beállítható a timeout értéke), illetve teljesen ki is kapcsolható a parkolás. Ez a nem RE2-es változathoz nem készült el. Miért?
  • Egyeseknek egy nem támogatott wdidle3.exe nevű program segítéségével sikerült kikapcsolni a funkciót. Másoknál ugyanez nem működik. Viszont valószínűleg ugrik a garancia, ha ezt használjuk.
  • A fentebb linkelt tudástári bejegyzés szerint Linux alatt kikapcsolható a hdparm -B 255 paranccsal a funkció. Nálam HDIO_DRIVE_CMD failed: Input/output error hibával leáll a hdparm, és tovább nő az LCC értéke. Támogatás nincs, mert a WD nem támogatja a merevlemezeit Linux/Unix alatt.
  • Saját tapasztalat: Egy éves WD10EADS, 70 ezer körüli Load_Cycle_Count érték, tökéletes működés.


    Frissítés, 2011. augusztus 12.:

    A témáról a Prohardver is cikkezett nemrég. Ők is a WD egyik support oldalára hivatkoznak (Answer ID 5357), ami szerint lehet használni a wdidle3.exe-t Caviar Green lemezekkel. Pontosan idézve a support oldalt: „Set Idle3 to max time (effectively turns off load/unload power saving feature thus will use more power) per below link.”

    A „below link”-et követve eljutunk az RE2GP Idle Mode Update Utility oldalára, ahonnan letölthető a wdidle3.exe egy figyelmeztetés mellett, mely szerint csak a WD1000FYPS-01ZKB0, WD7500AYPS-01ZKB0, WD7501AYPS-01ZKB0 modellekkel használható. Nem Raid Edition Green Power merevlemez nincs a listában. Az első lapon (Answer ID 5357) viszont Raid Edition merevlemez egyáltalán nincs említve.

    Az anomáliáról kérdeztem a hivatalos supportot is, szerintük nem támogatott a WD10EADS, és a többi nem Raid Edition lemezek használata wdidle3.exe-vel: „Unfortunately this software was made for a specific model of internal drives and the Caviar Green GP drive is not one of them. It was made for RE3 drives.” (Igen, RE3-at írtak a levélben.) A support oldalt persze azóta sem tették egyértelművé, hiába kértem.

    További adalék, hogy az eredeti, NGOHQ-n megjelent cikket a Western Digital PR is kommentálta, de a kommentben nyoma sincs tiltakozásnak, hogy a wdidle3.exe használata Caviar Green lemezekkel nem támogatott.

    Ezek után nem könnyű eldönteni, hogy merjük-e a használni a wdidle3.exe-t, esetlegesen kockáztatva a garanciát, vagy inkább hagyjuk szépen növekedni az LCC-t.

PRS-650 backup szkript

Lentebb egy biztonsági mentést készítő szkript Sony PRS-650-hez. Amit tud: automatikus mount/umount, könyvtárlétrehozás dátum alapján, md5sum az elkészült mentésről, a database/media/audio/ alatti tartalmak kihagyása.

#!/bin/bash

### Sony PRS-650 backup script by palacsint, 2011. 07. 14.
### http://palacsint.hu/

INPUT_DIR=/mnt/prs650_internal
BACKUP_BASE_DIR=/backup/sony-prs-650


mkdir --parents $BACKUP_BASE_DIR || exit -1

BACKUP_DIR=$BACKUP_BASE_DIR/sony-prs-650-$(date +%Y-%m-%d)
echo "Backup directory: $BACKUP_DIR"
if [ -d $BACKUP_DIR ]; then
    echo "Backup directory already exists, skip backup"
    exit -1
fi

echo "Free space on backup drive: $(df -h $BACKUP_BASE_DIR | tail -1 | awk '{print $4}')"

MOUNTED=false
if mountpoint -q $INPUT_DIR; then
    echo "Input directory $INPUT_DIR already mounted"
    MOUNTED=true
else
    mount $INPUT_DIR || exit -1
fi


mkdir --parents $BACKUP_DIR || exit -1

rsync --whole-file --recursive --filter='exclude, database/media/audio/**' $INPUT_DIR/ $BACKUP_DIR

find $INPUT_DIR | sed "s:$INPUT_DIR::" | sed 's:/::' > $BACKUP_DIR/file-list

cfv -q -C -t md5 -rr -p $BACKUP_DIR || exit -1

echo "Backup size: $(du -sh $BACKUP_DIR | awk '{print $1}')"

if [ "x$MOUNTED" = "xfalse" ]; then
    umount $INPUT_DIR
    sync
fi
echo "Backup OK"

Gzip/bzip2 tuning

Adott a következő könyvtár, amely napi mentéseket tartalmaz negyedévenként külön szülőkönyvtárban:

...
backup-2011q1/backup-2011-03-31
backup-2011q1/backup-2011-04-01
backup-2011q2/backup-2011-04-02
...

Az egyes napi mentések nem túl nagyok, de azért jó lenne egy idő után hatékonyan tömöríteni őket. Tart és gzipet vagy bzip2-t használva nem nyerünk sokat, a jelen esetben néhány száz megás, minimálisan eltérő napi mentések tömörített mérete csak 10-20 százalékkal kisebb, mint a könyvtárak tömörítetlen mérete.

A tar fájl a benne lévő fájlokat elérési útjuk szerinti ábécé sorrendben tárolja. Így a mindegyik napi mentésben előforduló azonos fájlok egymástól távol kerülnek a tar fájlban. A távolság miatt a gzip/bzip2 nem ismeri fel az ismétlődő mintákat, ezért nem tömörít hatékonyan. (Aki otthon van a témában, nyugodtan javítson ki, pontosítson kommentben.)

Gyors megoldás, ha gzip/bzip2 helyett 7-Zip-et használunk. Sajnos a gzip/bzip2 párost semmilyen paraméterrel nem sikerült rávennem, hogy maguktól felismerjék ezeket az ismétlődéseket, de a tar tartalmának megfelelő sorrendezésével segíthetünk a dolgon.

Erre két javasolt megoldás a credentiality blogon található. Az MD5 hash alapján történő sorrendezés megtalálható az előbbi linken, a fájlnév alapú sorrendezésre pedig egy példamegoldás a következő:

$ cat ~/bin/tar2 
#!/bin/bash
export GZIP=-9
find "$1" -type f | awk '{split($0, pathElements, "/"); print pathElements[length(pathElements)] "\t" $0 }' | sort | cut -f 2- | tar czvf "$1.tar.gz" -T -

Sajnos a tar tartalmának megfelelő sorrendezése nem segít az ismétlődő, de túl nagy fájlok tömörítésén, ezen ismétlődéseket a gzip/bzip2 továbbra sem fogja felismerni. A 7-Zip használata erre is megoldást jelent.

JAXB unmarshall utáni null listák eltüntetése

Az Effective Java című könyvben is szerepel, hogy nem ajánlott nullt visszaadni, inkább részesítsük előnyben az üres tömböket és az üres kollekciókat, ha tömb vagy kollekció a visszatérési érték típusa. Lentebb arra látható egy példa, hogy JAXB használatakor hogyan kell annotálni az osztályunkat, hogy az unmarshall után a JAXB ne hagyjon maga után nullokat a kollekciók értékeként.

import java.util.LinkedList;
import java.util.List;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement
@XmlAccessorType(XmlAccessType.NONE)
public class Data {

    @XmlElementWrapper(name = "list")
    @XmlElement(name = "list-element")
    private final List<String> list = new LinkedList<String>();

    public Data() {
    }

    public List<String> getList() {
        return new LinkedList<String>(list);
    }

    public void setList(final List<String> list) {
        if (list == null) {
            throw new IllegalArgumentException("list cannot be null");
        }
        this.list.clear();
        this.list.addAll(list);
    }
}

Érdemes megjegyezni, hogy ha a getter metódusra kerülne a @XmlElement annotáció, akkor a metódus által visszaadott listába kerülnének a JAXB által felvett elemek, emiatt a getterben nem lehetne másolatot visszaadni a belső listáról.

OpenOffice.org/LibreOffice-ból exportált PDF metaadatainak beállítása

Dokumentum címének beállítása:

  • File menü / Properties / Description fül alatt a Title mezőnél megadható a dokumentum címe.

A szerző megadása:

  • Tools menü / Options / LibreOffice / User Data oldalon a First name, Last name mezők kitöltése,
  • File menü / Properties alatt a General fülön pipáljuk ki az „Apply user data” jelölőnégyzetet,
  • nyomjuk meg az „Apply user data” melletti Reset gombot.

A reset gomb frissíti az épp szerkesztett dokumentum szerző mezőjét a User Data alatt megadott adatokkal, megnyomása után az új szerző rögtön megjelenik a General fülön a Created címke mellett. A dokumentumot pdf-ként exportálva ezután már a beállított értékek fognak szerepelni a metaadatok között. Ne felejtsük el elmenteni az eredeti dokumentumot sem a metaadatok módosítása után.

PDF metaadatok módosítása

A PDF toolkittel – vagy röviden csak pdftk-val – egyszerűen módosíthatóak a pdf fájlok metaadatai. A telepítés Debian alatt csak egy apt-get install pdftk, de van Windowsos bináris is, bár azt nem próbáltam.

A metaadatok közül a Sony PRS-650 esetében a szerző és a cím a legérdekesebb. A dátum szerinti sorrendezésnél a felmásolás dátuma számít, nem a pdf fájlban lévő időpont.

Metaadatok lekérdezése:

$ pdftk original.pdf dump_data > pdf.data

Az így létrejött pdf.data fájl tartalma valami ilyesmi:

InfoKey: Creator
InfoValue: Writer
InfoKey: Title
InfoValue: &#225;rv&#237;zt&#369;r&#337; t&#252;k&#246;rf&#250;r&#243;g&#233;p - &#193;RV&#205;ZT&#368;R&#336; T&#220;K&#214;RF&#218;R&#211;G&#201;P
InfoKey: Producer
InfoValue: OpenOffice.org 3.2
InfoKey: Author
InfoValue: palacsint
InfoKey: CreationDate
InfoValue: D:20110727211139+02'00'
PdfID0: d52f66ef5c592704640f4c44af42383
PdfID1: d52f66ef5c592704640f4c44af42383
NumberOfPages: 1

A címet az „InfoKey: Title” utáni sorban lévő InfoValue után kell megadni, a szerzőt pedig az „InfoKey: Author” utáni sorban. A cím jelen esetben az „árvíztűrő tükörfúrógép - ÁRVÍZTŰRŐ TÜKÖRFÚRÓGÉP” szöveg. Értelemszerűen az ékezetes karaktereket a fenti formában kell megadni.

Végül a pdf.data módosítása után a metaadatok visszatöltése egy új fájlba:

$ pdftk original.pdf update_info pdf.data output metafix.pdf

Mock objektumok többszálú használata

A lenti EasyMock (3.0) tesztben átadunk az executornak egy Runnable példányt. Ez egy mock objektum, és talán azt várnánk, hogy a verify() majd jelzi, hogy az executor (egy másik szálban) engedély nélkül meghívta a mock objektum run() metódusát, azaz hibával elszáll a teszt.

import static org.junit.Assert.assertTrue;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.easymock.EasyMock;
import org.junit.Test;

public class EasyMockTest {

	@Test
	public void testEasyMock() throws Exception {
		final Runnable runnable = EasyMock.createMock(Runnable.class);
		EasyMock.makeThreadSafe(runnable, true); // default true
		EasyMock.replay(runnable);

		runInNewThread(runnable);

		EasyMock.verify(runnable); // should fail
	}

	private void runInNewThread(final Runnable runnable)
			throws InterruptedException {
		final ExecutorService executor = Executors.newSingleThreadExecutor();
		executor.execute(runnable);

		executor.shutdown();
		final boolean terminated = executor.awaitTermination(2,
				TimeUnit.SECONDS);
		assertTrue("terminated", terminated);
	}
}

Nem ez történik, a teszt hiba nélkül lefut, a verify() nem jelez hibát. A konzolon viszont megjelenik a hibaüzenet:

Exception in thread "pool-1-thread-1" java.lang.AssertionError: 
  Unexpected method call run():
	at org.easymock.internal.MockInvocationHandler.invoke(MockInvocationHandler.java:45)
	at org.easymock.internal.ObjectMethodsFilter.invoke(ObjectMethodsFilter.java:73)
	at $Proxy4.run(Unknown Source)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)

JMock-kal (2.5.1) ugyanez a helyzet:

import static org.junit.Assert.assertTrue;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.jmock.Mockery;
import org.jmock.integration.junit4.JMock;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(JMock.class)
public class JmockTest {

	private Mockery mockery;

	@Before
	public void setUp() {
		mockery = new Mockery();
	}

	@Test // should fail
	public void testJmock() throws Exception {
		final Runnable runnable = mockery.mock(Runnable.class);

		runInNewThread(runnable);
	}

	...
}

A teszt sikeresen lefut, a hibaüzenet itt is csak a hibakonzolon jelenik meg.

Mockitót (1.8.5) használva jobbak az eredmények:

import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.junit.Test;

public class MockitoTest {

	@Test
	public void testMociko() throws Exception {
		final Runnable runnable = mock(Runnable.class);

		runInNewThread(runnable);

		try {
			verify(runnable, never()).run(); // failed
			fail("verify");
		} catch (final AssertionError expected) {

		}
	}

	...
}

A verify() hívás hibát dob, ahogy vártuk. A konzolon pedig nem jelenik meg semmi, mert mock() metódus úgynevezett nice mock objektumokat készít, amelyek csak rögzítik a meghívott metódusaikat és sosem dobnak hibát.

Az EasyMock-hoz egy nem túl szép workaround saját ThreadFactory használata a kivételt elkapó UncaughtExceptionHandler-rel, majd a teszt végén az UncaughtExceptionHandler ellenőrzése.

import static org.junit.Assert.assertTrue;

import java.lang.Thread.UncaughtExceptionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import org.easymock.EasyMock;
import org.junit.Test;

public class EasyMockExceptionHandlerTest {

	@Test
	public void testEasyMockWithUncaughtExceptionHandler() throws Exception {
		final Runnable runnable = EasyMock.createMock(Runnable.class);
		EasyMock.makeThreadSafe(runnable, true); // default true
		EasyMock.replay(runnable);

		final AtomicBoolean threadError = new AtomicBoolean(false);
		final ExecutorService executor = createExecutor(threadError);
		executor.execute(runnable);

		executor.shutdown();
		final boolean terminated = executor.awaitTermination(2,
				TimeUnit.SECONDS);
		assertTrue("terminated", terminated);

		EasyMock.verify(runnable);
		assertTrue("thread error", threadError.get());
	}

	private ExecutorService createExecutor(final AtomicBoolean threadError) {
		final ErrorFlagUncaughtExceptionHandler uncaughtExceptionHandler 
			= new ErrorFlagUncaughtExceptionHandler(threadError);

		final ThreadFactory threadFactory = new ThreadFactory() {
			final ThreadFactory defaultThreadFactory = Executors
					.defaultThreadFactory();

			public Thread newThread(final Runnable runnable) {
				final Thread thread = defaultThreadFactory.newThread(runnable);
				Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
				return thread;
			}
		};
		final ExecutorService executor = Executors
				.newSingleThreadExecutor(threadFactory);
		return executor;
	}

	private class ErrorFlagUncaughtExceptionHandler implements
			UncaughtExceptionHandler {

		private final AtomicBoolean threadError;

		public ErrorFlagUncaughtExceptionHandler(final AtomicBoolean threadError) {
			this.threadError = threadError;
		}

		public void uncaughtException(final Thread thread,
				final Throwable throwable) {
			threadError.set(true);
		}
	}
}

JMock-hoz talán a 2.6-os vagy 2.7-es verzióban lesz erre megoldás, a jelenlegi dokumentáció alapján nem igazán támogatott a JMock mock objektumainak többszálú használata. Lásd még: JMOCK-213,
JMOCK-183.

EasyMock: During the replay phase, mocks are by default thread-safe.

Ugyanez a helyzet a Mockitónál is: You can let multiple threads call methods on a shared mock to test in concurrent conditions.

Az igazi megoldást azonban nem mock-olásra használt osztálykönyvtárban kellene implementálni, hanem a tesztelt kódban, hogy a más szálakon keletkező hibákról a hívó értesüljön, valamint a tesztünk is ezen az úton tudja meg, hogy hiba történt. Az EasyMock levelezőlistáján olvasható erről egy levél:

„Let's say the exception would be thrown by a real implementation, and *not* by EasyMock. How would your application know that the calculation went wrong?”

Erre használható például az ExecutorService:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import org.easymock.EasyMock;
import org.junit.Test;

public class ExecutorServiceTest {

	@Test(expected = ExecutionException.class)
	public void testEasyMockWithExecutorService() throws Exception {
		final Runnable runnable = EasyMock.createMock(Runnable.class);
		EasyMock.makeThreadSafe(runnable, true); // default true
		EasyMock.replay(runnable);

		final ExecutorService executorService = Executors
				.newSingleThreadExecutor();

		final Future future = executorService.submit(runnable);
		future.get();
	}

}

Spielmaterial rendelés

Néhány tapasztalat egy spielmaterial.de csoportos rendelés kapcsán:

  • A postaköltséget csak rendelés leadása után számítják ki. Ez egy manuális lépés, általában másnapra elküldik az eredményt. Nálunk 10,5 és 18,5 euró között ingadozott. A szállítási költségre van ugyan néhány példa a weblapon, de eléggé közelítő jellegűek. Sajnos súlyinformáció sincs az egyes alkatrészek, termékek mellett.
  • A szállítási költségbe beleértik a PayPal költségét is. (Ez a plusz fél euró.)
  • Rendelés leadásának utolsó lépése a paypal.com-ra vezet, azonban nem muszáj előleget fizetni. (Alul van link, ami visszavisz a Spielmaterial oldalára.) A biztonság kedvéért a megjegyzés mezőbe mindig odaírtam, hogy csak a szállítási költségre vagyunk kíváncsiak, illetve majd egyben lesz fizetve, ha a részvevők átutalták a bankszámlámra a részüket.
  • Ha megvan a végleges lista, akkor megér még egy kérdést, hogy pár plusz tétel belefér-e ugyanabba a postaköltségbe, illetve ha kiveszünk egy-két nem túl fontos dolgot, akkor csökken-e esetleg a postaköltség.
  • A pénz esti elküldése utáni nap írták, hogy küldik ASAP a csomagot, ami az üzenet utáni negyedik munkanapon meg is érkezett a kért budapesti címre.
  • Próbáltam a Piatniknál is érdeklődni, hogy adnak-e „pótalkatrészeket” (igaz, nem egy-két darabról volt szó, hanem hetvenről), de válaszra sem méltattak.
  • Talán az ötödik rendelés volt az, aminél mindenki elfogadta a szállítási költség ráeső részét. Az eladói oldalon végig türelmesek voltak a sok leadott, majd lemondott rendelés ellenére is.
  • Az alapszínek (fehér, zöld, kék, sárga, piros, barna) szinte teljesen megegyeztek a magyar kiadású Catan színeivel. Kis eltérés a barna esetén volt. Ez sötétebb volt valamivel a Catanosnál.

Elveszett PRS-650 jegyzetek visszaállítása

A múltkor lefagyás után nagyrészt sikerült visszaállítani az elveszett jegyzeteimet. Rögtön az újraindítás után kivettem a memóriakártyát, hogy ne rontsam tovább a helyzetet. Ezután a korábbi adatmentős írásomban ismertetett módon lementettem az egész kártya tartalmát későbbi elemzésre.

Az mentett képfájlt a phororec-nek adtam oda, elég sok fájl hozott vissza. Az eredeti fájlnév nem maradt meg, úgy kellett összevadászni az ömlesztett fájlokból a database/cache/cacheExt.xml és a Digital Editions gyökérkönyvtárban lévő, annot kiterjesztésű, egyébként szintén XML fájlokra hasonlító fájlokat. Az XML-ek gyökértag-jére érdemes keresni („<cacheExt” és „<annotationSet”). A két fájl közül a cacheExt.xml fájlba kerülnek olvasható módon a jegyzetek, de ez valószínűleg – ahogy a neve is mutatja – csak egy cache, a tényleges jegyzeteket az annot fájl tárolja.

A photorec a fájlok legutolsó változatán kívül elég sok régebbi változatot is kibányászott a memóriakártya eldugott szektoraiból. Ezek közül a legnagyobbakat választottam ki. A fájlok mérete arányos a benne lévő jegyzetek számával.

Rossz hír, hogy ezt a két fájlt szinkronban kell visszaállítani. Ez nekem nem akart összejönni. Az .annot fájl törlése után a cacheExt-ből is törlődtek az annotációk, viszont az annot fájl visszaállítása után nem generálódik újra a cache, hanem az annot fájl tartalma is törlődik. (Elég érdekes viselkedés egy cache-hez képest.) A kettő egy időben történő visszaállításakor pedig némelyik jegyzet nem volt kijelölve a szövegben, csak a jegyzetlistán jelent meg a hozzá tartozó bejegyzés.

Az annot fájlból és a könyvből kiindulva valószínűleg le lehetne generálni a cacheExt tartalmát is, ha máshogy nem is, a firmware forráskódja alapján biztosan, de ennyit nem ért meg a dolog. Ehelyett inkább kinyomtattam a legnagyobb cacheExt fájl releváns részeit és kézzel felvettem újra a jegyzeteket.

A cacheExt-ben ezek a részek az érdekesek:

<annotation date="Tue, 16 Nov 2010 22:10:42 GMT" name="..." page="1" pageOffset="1"
	pages="948" part="0" scale="2" synced="true" dpi="167" width="584" height="754" 
	layoutVersion="1">
		<start>...</start>
		<end>...</end>
		<comment></comment>
</annotation>

A name attribútum tárolja a kijelölt szöveget, a page az oldalszámot (0 jelöli az első oldalt), a scale a kijelöléskori nagyítást (0 = S, 1 = M, 2 = L), a pageOffset pedig az adott fizikai oldalon belüli oldalt (nagyítás esetén van jelentősége).

Képek, rajzok visszaállítása

A rajzokat külön SVG fájlok tárolják, az XML-ből csak hivatkozás van rájuk. A hivatkozás csak fájlnévvel történik, ami eléggé kellemetlen, mivel a photorec nem állítja vissza az eredeti fájlnevet.

A cacheExt-beli tag hasonló az előzőhöz:

<freehand date="Sun, 28 Nov 2010 18:16:31 GMT" name="..." page="785" pageOffset="0"
	pages="948" part="0" scale="0" synced="true" dpi="167" width="584" height="754"
	layoutVersion="1">
		<svgFile width="584" height="754">1290968182066.77.svg</svgFile>
		<thumbFile width="115" height="149">1290968182066.77.jpg</thumbFile>
		<mark>...</mark>
		<comment></comment>
</freehand>

A jpg fájl tartalmazza a rajz mögötti könyvoldal képét is, ami alapján találgatással össze lehet párosítani a photorec által megtalált képeket az XML freehand bejegyzéseivel. A könyvből ki kell keresni a page attribútumokban szereplő oldalakat, majd az ezekhez hasonlító képet. Ezek a képek nagyon kicsik (kb. 100 x 150 képpont), rossz minőségűek, de nekem sokat segítettek, mert esetemben az ábrák többnyire csak egy pipából álltak a már olvasott fejezetek elején, és ezek az oldalak változatosabb képet mutattak, mint a folytonos szöveg a fejezetek további oldalain. Érdemes figyelni (nem sorkizárt szöveg esetén) a sorok végére rajzolható görbét, a fejezetcím hosszát, ha eltérő, akkor a páros-páratlan oldalak közti fejléc, illetve lábléc különbségét, ábrák alakját.

Utoljára olvasott oldal visszaállítása

A cacheExt fájlból a következő részlet az érdekes:

<text path="...pdf" preventDelete="true" opened="true">
	<currentPosition date="Fri, 07 Jan 2011 13:32:25 GMT" name="undefined" 
		page="552" pageOffset="0" pages="948" part="0" scale="0" synced="false"
		dpi="167" width="584" height="754" layoutVersion="1">
			<mark>I3BkZmxvYyg5OWYwLDU1MikA</mark>
			<comment></comment>
	</currentPosition>
	...

Itt is a korábban már ismertetett page, scale és a pageOffset attribútumokra kell figyelni.

Fontok beágyazása epub fájlokba – kicsit részletesebben

Amire szükség lesz:

A fontok tar.gz-ben érkeznek, ami például a 7-Zippel kicsomagolható. A kitömörített könyvtárból a következő fájlokra lesz szükség:

  • LiberationSerif-BoldItalic.ttf
  • LiberationSerif-Bold.ttf
  • LiberationSerif-Italic.ttf
  • LiberationSerif-Regular.ttf
    • Ízlés szerint persze lehet más fontokat is használni. A Linux Libertine fonttal a Sony PRS-650 esetében nem volt szerencsém, az eszköz nem kezelte megfelelően, néha kimaradtak bekezdések a könyvekből. A font lecserélése megoldotta a problémát. A Liberation fonttal még nem volt problémám, a Sony jól kezeli őket.

      A Sigil telepítése után nyissuk meg az epub fájlt a File menü Open menüpontjával. A megnyitott fájl Book Browser ablakában a Fonts mappára kattintva jobbklikk, Add Existsing Items. Keressük meg a fenti négy ttf fájl, és adjuk hozzá a listához.

      Ezután szintén Book Browser ablak, jobbklikk a Styles könyvtáron, Add New Item. Ha jól csináltuk, akkor létrejön egy Style0001.css nevű fájl. Duplaklikk a fájlnévre, majd másoljuk be az előző bejegyzésben is szereplő CSS tartalmat:

      @font-face {
      	font-family: "LiberationSerif";
      	font-style: normal;
      	font-weight: normal;
      	src:url(../Fonts/LiberationSerif-Regular.ttf)
      }
      
      @font-face {
      	font-family: "LiberationSerif";
      	font-style: italic;
      	font-weight: normal;
      	src:url(../Fonts/LiberationSerif-Italic.ttf)
      }
      
      @font-face {
      	font-family: "LiberationSerif";
      	font-style: normal;
      	font-weight: bold;
      	src:url(../Fonts/LiberationSerif-Bold.ttf)
      }
      
      @font-face {
      	font-family: "LiberationSerif";
      	font-style: italic;
      	font-weight: bold;
      	src:url(../Fonts/LiberationSerif-BoldItalic.ttf)
      }
      
      body { 
      	font-family: "LiberationSerif"
      }
      

      A CSS-ben az src:url kezdetű sorokban lévő fájlnévnek egyeznie kell a fenti listában szereplő fájlnevekkel. Ez alapesetben így van, ha mégsem működne valami, akkor ezt mindenképp ellenőrizzük, illetve ha más fontot használnánk, akkor figyeljünk rá.

      Ha ezzel megvagyunk, akkor a Book Browser ablakban nyissuk meg az xhtml fájlokat, majd váltsunk át Code View-ra (View menü / Code View menüpont, vagy F11). A fájlok elején, a <head> és </head> közé helyezzük el a következő tag-et:

      <link href="../Styles/Style0001.css" rel="stylesheet" type="text/css" />
      

      Például:

      <?xml version="1.0"?>
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
        "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
      
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
      	<title>Liberation Serif Font Test</title>
      	<link href="../Styles/Style0001.css" rel="stylesheet" type="text/css" />
      </head>
      
      <body>
        <p>GYÜMÖLCSVÉDŐ ÁGYÚFŰNYÍRÓ</p>
        <p>Gyümölcsvédő ágyúfűnyíró</p>
      </body>
      </html>
      

      A <head></head> között meglévő tartalom maradjon meg, azt ne töröljük.

      Fontos, hogy beszúrt tag-ben a href után lévő fájlnévnek egyeznie kell a korábban létrehozott CSS nevével (Style0001.css), valamint a fenti link tag-nek minden xhtml fájlban szerepelnie kell.

      Ha kész, akkor a Tools menü Validate Epub menüvel ellenőrizhető, hogy az eredmény megfelel-e az epub szabványnak. Jó tudni, hogy a validátor (jelenleg) sajnos nem ellenőrzi a CSS-ben lévő hivatkozásokat. Az ellenőrzést érdemes a módosítások megkezdése előtt is, hogy a forrásban lévő hibákkal ne itt találkozzunk először.

      Végül innen letölthető egy mintaepub, ami tartalmazza a fentieket. Kérdés nyugodtan jöhet kommentben vagy e-mailben.

Tartalom átvétel