mirror of
https://github.com/geometer/FBReaderJ.git
synced 2025-10-05 10:49:24 +02:00
OPDS catalogs cache
git-svn-id: https://only.mawhrin.net/repos/FBReaderJ/trunk@1675 6a642e6f-84f6-412e-ac94-c4a38d5a04b0
This commit is contained in:
parent
d029660d2e
commit
105b518a46
22 changed files with 370 additions and 59 deletions
|
@ -89,5 +89,6 @@
|
||||||
<service android:name="org.geometerplus.android.fbreader.network.ItemsLoadingService" android:process=":networkLibrary" />
|
<service android:name="org.geometerplus.android.fbreader.network.ItemsLoadingService" android:process=":networkLibrary" />
|
||||||
<activity android:name="org.geometerplus.android.fbreader.network.NetworkBookInfoActivity" android:process=":networkLibrary" android:configChanges="orientation|keyboardHidden" />
|
<activity android:name="org.geometerplus.android.fbreader.network.NetworkBookInfoActivity" android:process=":networkLibrary" android:configChanges="orientation|keyboardHidden" />
|
||||||
<receiver android:name="org.geometerplus.android.fbreader.network.BookDownloaderCallback" android:process=":networkLibrary" />
|
<receiver android:name="org.geometerplus.android.fbreader.network.BookDownloaderCallback" android:process=":networkLibrary" />
|
||||||
|
<service android:name="org.geometerplus.android.fbreader.network.LibraryInitializationService" android:process=":networkLibrary" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -89,5 +89,6 @@
|
||||||
<service android:name="org.geometerplus.android.fbreader.network.ItemsLoadingService" android:process=":networkLibrary" />
|
<service android:name="org.geometerplus.android.fbreader.network.ItemsLoadingService" android:process=":networkLibrary" />
|
||||||
<activity android:name="org.geometerplus.android.fbreader.network.NetworkBookInfoActivity" android:process=":networkLibrary" android:configChanges="orientation|keyboardHidden" />
|
<activity android:name="org.geometerplus.android.fbreader.network.NetworkBookInfoActivity" android:process=":networkLibrary" android:configChanges="orientation|keyboardHidden" />
|
||||||
<receiver android:name="org.geometerplus.android.fbreader.network.BookDownloaderCallback" android:process=":networkLibrary" />
|
<receiver android:name="org.geometerplus.android.fbreader.network.BookDownloaderCallback" android:process=":networkLibrary" />
|
||||||
|
<service android:name="org.geometerplus.android.fbreader.network.LibraryInitializationService" android:process=":networkLibrary" />
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -15,7 +15,8 @@ NP: оповещение об изменениях в namespace'ах проис
|
||||||
** Загрузка не из xml, а из OPDS-каталога с расширениями
|
** Загрузка не из xml, а из OPDS-каталога с расширениями
|
||||||
DONE сделать DTD для xml-ей, как в OPDS, вместо существующего
|
DONE сделать DTD для xml-ей, как в OPDS, вместо существующего
|
||||||
DONE перенести xml на сервер
|
DONE перенести xml на сервер
|
||||||
** кэширование каталога
|
DONE кэширование каталога
|
||||||
|
** реализовать обновление библиотеки так, чтобы не терялись загруженные элементы дерева
|
||||||
** сделать чтение информации о поиске напрямую из каталога
|
** сделать чтение информации о поиске напрямую из каталога
|
||||||
|
|
||||||
** Использовать default e-mail при регистрации новых пользователей в AuthenticationDialog (возможно только в Android 2.0+)
|
** Использовать default e-mail при регистрации новых пользователей в AuthenticationDialog (возможно только в Android 2.0+)
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="Operation wird nicht unterstützt" />
|
<node name="unsupportedOperation" value="Operation wird nicht unterstützt" />
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Information" />
|
<node name="title" value="Information" />
|
||||||
|
|
|
@ -295,6 +295,7 @@
|
||||||
<node name="unsupportedOperation" value="Unsupported operation" />
|
<node name="unsupportedOperation" value="Unsupported operation" />
|
||||||
<node name="notAnOPDS" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Information" />
|
<node name="title" value="Information" />
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="Opération non supportée"/>
|
<node name="unsupportedOperation" value="Opération non supportée"/>
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Information"/>
|
<node name="title" value="Information"/>
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="nem támogaott művelet" />
|
<node name="unsupportedOperation" value="nem támogaott művelet" />
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Információ" />
|
<node name="title" value="Információ" />
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="Operazione non supportata" />
|
<node name="unsupportedOperation" value="Operazione non supportata" />
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Informazioni" />
|
<node name="title" value="Informazioni" />
|
||||||
|
|
|
@ -295,6 +295,7 @@
|
||||||
<node name="unsupportedOperation" value="Неподдерживаемая операция" />
|
<node name="unsupportedOperation" value="Неподдерживаемая операция" />
|
||||||
<node name="notAnOPDS" value="Это не OPDS каталог" />
|
<node name="notAnOPDS" value="Это не OPDS каталог" />
|
||||||
<node name="noRequiredInformation" value="Требуемая информация не найдена в каталоге" />
|
<node name="noRequiredInformation" value="Требуемая информация не найдена в каталоге" />
|
||||||
|
<node name="cacheDirectoryError" value="Не удается создать каталог для кэша" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Информация" />
|
<node name="title" value="Информация" />
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="Thao tác không được hỗ trợ" />
|
<node name="unsupportedOperation" value="Thao tác không được hỗ trợ" />
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="Thông tin" />
|
<node name="title" value="Thông tin" />
|
||||||
|
|
|
@ -296,6 +296,7 @@
|
||||||
<node name="unsupportedOperation" value="非法操作" />
|
<node name="unsupportedOperation" value="非法操作" />
|
||||||
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
<node name="notAnOPDS" toBeTranslated="true" value="This is not an OPDS catalog" />
|
||||||
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
<node name="noRequiredInformation" toBeTranslated="true" value="Required information is not specified in the catalog" />
|
||||||
|
<node name="cacheDirectoryError" toBeTranslated="true" value="Unable to create cache directory" />
|
||||||
</node>
|
</node>
|
||||||
<node name="emptyCatalogBox">
|
<node name="emptyCatalogBox">
|
||||||
<node name="title" value="信息" />
|
<node name="title" value="信息" />
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2010 Geometer Plus <contact@geometerplus.com>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation; either version 2 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program; if not, write to the Free Software
|
||||||
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||||
|
* 02110-1301, USA.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.geometerplus.android.fbreader.network;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.Intent;
|
||||||
|
|
||||||
|
|
||||||
|
public class LibraryInitializationService extends Service {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart(Intent intent, int startId) {
|
||||||
|
super.onStart(intent, startId);
|
||||||
|
|
||||||
|
final NetworkView view = NetworkView.Instance();
|
||||||
|
if (!view.isInitialized()) {
|
||||||
|
stopSelf();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Handler handler = new Handler() {
|
||||||
|
@Override
|
||||||
|
public void handleMessage(Message msg) {
|
||||||
|
if (msg.what > 0) {
|
||||||
|
view.finishBackgroundUpdate();
|
||||||
|
}
|
||||||
|
stopSelf();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Thread thread = new Thread(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
boolean result = false;
|
||||||
|
try {
|
||||||
|
result = view.runBackgroundUpdate();
|
||||||
|
} finally {
|
||||||
|
handler.sendEmptyMessage(result ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setPriority(Thread.MIN_PRIORITY);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,7 @@ package org.geometerplus.android.fbreader.network;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
@ -59,10 +60,6 @@ public class NetworkLibraryActivity extends NetworkBaseActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
tryResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tryResume() {
|
|
||||||
if (!NetworkView.Instance().isInitialized()) {
|
if (!NetworkView.Instance().isInitialized()) {
|
||||||
new Initializator().start();
|
new Initializator().start();
|
||||||
} else {
|
} else {
|
||||||
|
@ -114,6 +111,7 @@ public class NetworkLibraryActivity extends NetworkBaseActivity {
|
||||||
if (message.what == 0) {
|
if (message.what == 0) {
|
||||||
runInitialization(); // run initialization process
|
runInitialization(); // run initialization process
|
||||||
} else if (message.obj == null) {
|
} else if (message.obj == null) {
|
||||||
|
startService(new Intent(getApplicationContext(), LibraryInitializationService.class));
|
||||||
prepareView(); // initialization is complete successfully
|
prepareView(); // initialization is complete successfully
|
||||||
} else {
|
} else {
|
||||||
processResults((String) message.obj); // handle initialization error
|
processResults((String) message.obj); // handle initialization error
|
||||||
|
|
|
@ -79,6 +79,19 @@ class NetworkView {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This method must be called from background thread
|
||||||
|
public boolean runBackgroundUpdate() {
|
||||||
|
return NetworkLibrary.Instance().runBackgroundUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method MUST be called from main thread
|
||||||
|
// This method can be called only after runBackgroundUpdate method has returned true
|
||||||
|
public void finishBackgroundUpdate() {
|
||||||
|
NetworkLibrary library = NetworkLibrary.Instance();
|
||||||
|
library.finishBackgroundUpdate();
|
||||||
|
library.synchronize();
|
||||||
|
fireModelChangedInternal();
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* NetworkLibraryItem's actions
|
* NetworkLibraryItem's actions
|
||||||
|
|
|
@ -29,4 +29,8 @@ public abstract class Paths {
|
||||||
public static String cacheDirectory() {
|
public static String cacheDirectory() {
|
||||||
return BooksDirectoryOption.getValue() + "/.FBReader";
|
return BooksDirectoryOption.getValue() + "/.FBReader";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String networkCacheDirectory() {
|
||||||
|
return cacheDirectory() + "/cache";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ import java.io.*;
|
||||||
|
|
||||||
import org.geometerplus.zlibrary.core.image.ZLBase64EncodedImage;
|
import org.geometerplus.zlibrary.core.image.ZLBase64EncodedImage;
|
||||||
|
|
||||||
|
import org.geometerplus.fbreader.Paths;
|
||||||
|
|
||||||
final class Base64EncodedImage extends ZLBase64EncodedImage {
|
final class Base64EncodedImage extends ZLBase64EncodedImage {
|
||||||
|
|
||||||
private static final String ENCODED_SUFFIX = ".base64";
|
private static final String ENCODED_SUFFIX = ".base64";
|
||||||
|
@ -36,7 +38,7 @@ final class Base64EncodedImage extends ZLBase64EncodedImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String makeImagesDir() {
|
public static String makeImagesDir() {
|
||||||
return NetworkImage.makeImagesDir() + File.separator + "base64";
|
return Paths.networkCacheDirectory() + "/base64";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setData(String data) {
|
public void setData(String data) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ import java.io.*;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
|
||||||
import org.geometerplus.zlibrary.core.image.ZLSingleImage;
|
import org.geometerplus.zlibrary.core.image.ZLSingleImage;
|
||||||
import org.geometerplus.zlibrary.core.network.*;
|
import org.geometerplus.zlibrary.core.network.ZLNetworkManager;
|
||||||
|
|
||||||
import org.geometerplus.fbreader.Paths;
|
import org.geometerplus.fbreader.Paths;
|
||||||
|
|
||||||
|
@ -40,15 +40,11 @@ public final class NetworkImage extends ZLSingleImage {
|
||||||
public NetworkImage(String url, String mimeType) {
|
public NetworkImage(String url, String mimeType) {
|
||||||
super(mimeType);
|
super(mimeType);
|
||||||
Url = url;
|
Url = url;
|
||||||
new File(makeImagesDir()).mkdirs();
|
new File(Paths.networkCacheDirectory()).mkdirs();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TOESCAPE = "<>:\"|?*\\";
|
private static final String TOESCAPE = "<>:\"|?*\\";
|
||||||
|
|
||||||
public static String makeImagesDir() {
|
|
||||||
return Paths.cacheDirectory() + File.separator + "cache";
|
|
||||||
}
|
|
||||||
|
|
||||||
// mimeType string MUST be interned
|
// mimeType string MUST be interned
|
||||||
public static String makeImageFileName(String url, String mimeType) {
|
public static String makeImageFileName(String url, String mimeType) {
|
||||||
URI uri;
|
URI uri;
|
||||||
|
@ -65,7 +61,7 @@ public final class NetworkImage extends ZLSingleImage {
|
||||||
path.delete(0, 4);
|
path.delete(0, 4);
|
||||||
}
|
}
|
||||||
path.insert(0, File.separator);
|
path.insert(0, File.separator);
|
||||||
path.insert(0, makeImagesDir());
|
path.insert(0, Paths.networkCacheDirectory());
|
||||||
|
|
||||||
int index = path.length();
|
int index = path.length();
|
||||||
|
|
||||||
|
@ -191,24 +187,7 @@ public final class NetworkImage extends ZLSingleImage {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZLNetworkManager.Instance().perform(new ZLNetworkRequest(Url) {
|
ZLNetworkManager.Instance().downloadToFile(Url, imageFile);
|
||||||
public String handleStream(URLConnection connection, InputStream inputStream) throws IOException {
|
|
||||||
OutputStream outStream = new FileOutputStream(imageFile);
|
|
||||||
try {
|
|
||||||
final byte[] buffer = new byte[8192];
|
|
||||||
while (true) {
|
|
||||||
final int size = inputStream.read(buffer);
|
|
||||||
if (size <= 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
outStream.write(buffer, 0, size);
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
outStream.close();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
mySynchronized = true;
|
mySynchronized = true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -232,15 +232,11 @@ public class NetworkLibrary {
|
||||||
public String initialize() {
|
public String initialize() {
|
||||||
final LinksComparator comparator = new LinksComparator();
|
final LinksComparator comparator = new LinksComparator();
|
||||||
|
|
||||||
final String url = "http://data.fbreader.org/catalogs/generic-1.0.xml";
|
final String error = OPDSLinkReader.loadOPDSLinks(false, new OnNewLinkListener() {
|
||||||
|
public void onNewLink(INetworkLink link) {
|
||||||
final String error = ZLNetworkManager.Instance().perform(
|
addLinkInternal(myLoadedLinks, link, comparator);
|
||||||
OPDSLinkReader.loadOPDSLinksRequest(url, new OnNewLinkListener() {
|
}
|
||||||
public void onNewLink(INetworkLink link) {
|
});
|
||||||
addLinkInternal(myLoadedLinks, link, comparator);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
synchronized (myLinks) {
|
synchronized (myLinks) {
|
||||||
|
@ -262,9 +258,84 @@ public class NetworkLibrary {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/*testDate(new ATOMUpdated(2010, 1, 1, 1, 0, 0, 0, 2, 0),
|
||||||
|
new ATOMUpdated(2009, 12, 31, 23, 0, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2010, 12, 31, 23, 40, 0, 0, -1, -30),
|
||||||
|
new ATOMUpdated(2011, 1, 1, 1, 10, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2010, 1, 31, 23, 40, 0, 0, -1, -30),
|
||||||
|
new ATOMUpdated(2010, 2, 1, 1, 10, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2010, 2, 28, 23, 40, 0, 0, -1, -30),
|
||||||
|
new ATOMUpdated(2010, 3, 1, 1, 10, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2012, 2, 28, 23, 40, 0, 0, -1, -30),
|
||||||
|
new ATOMUpdated(2012, 2, 29, 1, 10, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2012, 2, 15, 23, 40, 0, 0, -1, -30),
|
||||||
|
new ATOMUpdated(2012, 2, 16, 1, 10, 0, 0, 0, 0));
|
||||||
|
testDate(new ATOMUpdated(2012, 2, 15, 23, 40, 1, 0, 3, 30),
|
||||||
|
new ATOMUpdated(2012, 2, 15, 23, 40, 0, 0, 3, 30));
|
||||||
|
testDate(new ATOMUpdated(2012, 2, 15, 23, 40, 0, 0, 3, 30),
|
||||||
|
new ATOMUpdated(2012, 2, 15, 23, 40, 1, 0, 3, 30));
|
||||||
|
testDate(new ATOMUpdated(2012, 2, 15, 23, 40, 0, 0.001f, 3, 30),
|
||||||
|
new ATOMUpdated(2012, 2, 15, 23, 40, 0, 0, 3, 30));*/
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*private void testDate(ATOMDateConstruct date1, ATOMDateConstruct date2) {
|
||||||
|
String sign = " == ";
|
||||||
|
final int diff = date1.compareTo(date2);
|
||||||
|
if (diff > 0) {
|
||||||
|
sign = " > ";
|
||||||
|
} else if (diff < 0) {
|
||||||
|
sign = " < ";
|
||||||
|
}
|
||||||
|
Log.w("FBREADER", "" + date1 + sign + date2);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
private ArrayList<INetworkLink> myBackgroundLinks;
|
||||||
|
private Object myBackgroundLock = new Object();
|
||||||
|
|
||||||
|
// This method must be called from background thread
|
||||||
|
public boolean runBackgroundUpdate() {
|
||||||
|
synchronized (myBackgroundLock) {
|
||||||
|
myBackgroundLinks = new ArrayList<INetworkLink>();
|
||||||
|
|
||||||
|
final String error = OPDSLinkReader.loadOPDSLinks(true, new OnNewLinkListener() {
|
||||||
|
public void onNewLink(INetworkLink link) {
|
||||||
|
myBackgroundLinks.add(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error != null || myBackgroundLinks.isEmpty()) {
|
||||||
|
myBackgroundLinks = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (myBackgroundLinks == null) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
Collections.sort(myBackgroundLinks, new LinksComparator());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method MUST be called from main thread
|
||||||
|
// This method can be called only after runBackgroundUpdate method has returned true
|
||||||
|
//
|
||||||
|
// synchronize() method MUST be called after this method
|
||||||
|
public void finishBackgroundUpdate() {
|
||||||
|
synchronized (myBackgroundLock) {
|
||||||
|
if (myBackgroundLinks == null) {
|
||||||
|
throw new RuntimeException("Invalid state: that's impossible!!!");
|
||||||
|
}
|
||||||
|
synchronized (myLinks) {
|
||||||
|
myLoadedLinks.clear();
|
||||||
|
myLoadedLinks.addAll(myBackgroundLinks);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public String rewriteUrl(String url, boolean externalUrl) {
|
public String rewriteUrl(String url, boolean externalUrl) {
|
||||||
final String host = ZLNetworkUtil.hostFromUrl(url).toLowerCase();
|
final String host = ZLNetworkUtil.hostFromUrl(url).toLowerCase();
|
||||||
synchronized (myLinks) {
|
synchronized (myLinks) {
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
|
|
||||||
package org.geometerplus.fbreader.network.atom;
|
package org.geometerplus.fbreader.network.atom;
|
||||||
|
|
||||||
public abstract class ATOMDateConstruct extends ATOMCommonAttributes {
|
public abstract class ATOMDateConstruct extends ATOMCommonAttributes implements Comparable<ATOMDateConstruct> {
|
||||||
|
|
||||||
public int Year;
|
public int Year;
|
||||||
public int Month;
|
public int Month;
|
||||||
|
@ -278,5 +278,51 @@ public abstract class ATOMDateConstruct extends ATOMCommonAttributes {
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return getDateTime(false);
|
return getDateTime(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int[] DAYS_IN_MONTHS = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||||
|
private int daysInMonth(int month, int year) {
|
||||||
|
--month;
|
||||||
|
while (month > 11) month -= 12;
|
||||||
|
while (month < 0) month += 12;
|
||||||
|
if (month == 1 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) {
|
||||||
|
return DAYS_IN_MONTHS[1] + 1;
|
||||||
|
}
|
||||||
|
return DAYS_IN_MONTHS[month];
|
||||||
|
}
|
||||||
|
|
||||||
|
public int compareTo(ATOMDateConstruct date) {
|
||||||
|
int dateYear = date.Year;
|
||||||
|
int dateMonth = date.Month;
|
||||||
|
int dateDay = date.Day;
|
||||||
|
int dateHour = date.Hour;
|
||||||
|
int dateMinutes = date.Minutes;
|
||||||
|
if (TZHour != date.TZHour || TZMinutes != date.TZMinutes) {
|
||||||
|
dateMinutes += TZMinutes - date.TZMinutes;
|
||||||
|
while (dateMinutes < 0) { dateMinutes += 60; --dateHour; }
|
||||||
|
while (dateMinutes > 59) { dateMinutes -= 60; ++dateHour; }
|
||||||
|
dateHour += TZHour - date.TZHour;
|
||||||
|
while (dateHour < 0) { dateHour += 24; --dateDay; }
|
||||||
|
while (dateHour > 23) { dateHour -= 24; ++dateDay; }
|
||||||
|
while (dateDay < 1) dateDay += daysInMonth(--dateMonth, dateYear);
|
||||||
|
while (dateDay > daysInMonth(dateMonth, dateYear)) dateDay -= daysInMonth(dateMonth++, dateYear);
|
||||||
|
while (dateMonth < 1) { dateMonth += 12; --dateYear; }
|
||||||
|
while (dateMonth > 12) { dateMonth -= 12; ++dateYear; }
|
||||||
|
}
|
||||||
|
if (Year != dateYear) return Year - dateYear;
|
||||||
|
if (Month != dateMonth) return Month - dateMonth;
|
||||||
|
if (Day != dateDay) return Day - dateDay;
|
||||||
|
if (Hour != dateHour) return Hour - dateHour;
|
||||||
|
if (Minutes != dateMinutes) return Minutes - dateMinutes;
|
||||||
|
if (Seconds != date.Seconds) return Seconds - date.Seconds;
|
||||||
|
return Math.round(100 * SecondFraction) - Math.round(100 * date.SecondFraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (!(obj instanceof ATOMDateConstruct)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return compareTo((ATOMDateConstruct) obj) == 0;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -19,18 +19,23 @@
|
||||||
|
|
||||||
package org.geometerplus.fbreader.network.opds;
|
package org.geometerplus.fbreader.network.opds;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.FileInputStream;
|
||||||
import java.net.URLConnection;
|
import java.io.FileNotFoundException;
|
||||||
import java.util.*;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.geometerplus.zlibrary.core.network.ZLNetworkRequest;
|
import org.geometerplus.zlibrary.core.network.ZLNetworkManager;
|
||||||
|
|
||||||
|
import org.geometerplus.fbreader.Paths;
|
||||||
import org.geometerplus.fbreader.network.*;
|
import org.geometerplus.fbreader.network.*;
|
||||||
|
import org.geometerplus.fbreader.network.atom.ATOMUpdated;
|
||||||
|
|
||||||
|
|
||||||
public class OPDSLinkReader {
|
public class OPDSLinkReader {
|
||||||
|
|
||||||
|
static final String CATALOGS_URL = "http://data.fbreader.org/catalogs/generic-1.0.xml";
|
||||||
|
|
||||||
public static ICustomNetworkLink createCustomLink(int id, String siteName, String title, String summary, String icon, Map<String, String> links) {
|
public static ICustomNetworkLink createCustomLink(int id, String siteName, String title, String summary, String icon, Map<String, String> links) {
|
||||||
if (siteName == null || title == null || links.get(INetworkLink.URL_MAIN) == null) {
|
if (siteName == null || title == null || links.get(INetworkLink.URL_MAIN) == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -44,13 +49,71 @@ public class OPDSLinkReader {
|
||||||
return new OPDSCustomLink(ICustomNetworkLink.INVALID_ID, siteName, null, null, null, links);
|
return new OPDSCustomLink(ICustomNetworkLink.INVALID_ID, siteName, null, null, null, links);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ZLNetworkRequest loadOPDSLinksRequest(String url, final NetworkLibrary.OnNewLinkListener listener) {
|
|
||||||
return new ZLNetworkRequest(url) {
|
public static String loadOPDSLinks(boolean updateNotLoad, final NetworkLibrary.OnNewLinkListener listener) {
|
||||||
@Override
|
final File dirFile = new File(Paths.networkCacheDirectory());
|
||||||
public String handleStream(URLConnection connection, InputStream inputStream) throws IOException {
|
if (!dirFile.exists() && !dirFile.mkdirs()) {
|
||||||
new OPDSLinkXMLReader(listener).read(inputStream);
|
return NetworkErrors.errorMessage("cacheDirectoryError");
|
||||||
return null;
|
}
|
||||||
|
|
||||||
|
final String fileName = "fbreader_catalogs-"
|
||||||
|
+ CATALOGS_URL.substring(CATALOGS_URL.lastIndexOf(File.separator) + 1);
|
||||||
|
|
||||||
|
boolean goodCache = false;
|
||||||
|
File oldCache = null;
|
||||||
|
ATOMUpdated cacheUpdatedTime = null;
|
||||||
|
final File catalogsFile = new File(dirFile, fileName);
|
||||||
|
if (catalogsFile.exists()) {
|
||||||
|
if (updateNotLoad) {
|
||||||
|
try {
|
||||||
|
final OPDSLinkXMLReader reader = new OPDSLinkXMLReader();
|
||||||
|
reader.read(new FileInputStream(catalogsFile));
|
||||||
|
cacheUpdatedTime = reader.getUpdatedTime();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new RuntimeException("That's impossible!!!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
final long diff = System.currentTimeMillis() - catalogsFile.lastModified();
|
||||||
|
final long valid = 7 * 24 * 60 * 60 * 1000; // one week in milliseconds; FIXME: hardcoded const
|
||||||
|
if (diff >= 0 && diff <= valid) {
|
||||||
|
goodCache = true;
|
||||||
|
} else {
|
||||||
|
oldCache = new File(dirFile, "_" + fileName);
|
||||||
|
oldCache.delete();
|
||||||
|
if (!catalogsFile.renameTo(oldCache)) {
|
||||||
|
catalogsFile.delete();
|
||||||
|
oldCache = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
goodCache = true;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
|
String error = null;
|
||||||
|
if (!goodCache) {
|
||||||
|
error = ZLNetworkManager.Instance().downloadToFile(CATALOGS_URL, catalogsFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
if (oldCache == null) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
catalogsFile.delete();
|
||||||
|
if (!oldCache.renameTo(catalogsFile)) {
|
||||||
|
oldCache.delete();
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
} else if (oldCache != null) {
|
||||||
|
oldCache.delete();
|
||||||
|
oldCache = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new OPDSLinkXMLReader(listener, cacheUpdatedTime).read(new FileInputStream(catalogsFile));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
throw new RuntimeException("That's impossible!!!", e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import org.geometerplus.fbreader.network.INetworkLink;
|
||||||
import org.geometerplus.fbreader.network.NetworkImage;
|
import org.geometerplus.fbreader.network.NetworkImage;
|
||||||
import org.geometerplus.fbreader.network.NetworkLibrary;
|
import org.geometerplus.fbreader.network.NetworkLibrary;
|
||||||
import org.geometerplus.fbreader.network.atom.ATOMLink;
|
import org.geometerplus.fbreader.network.atom.ATOMLink;
|
||||||
|
import org.geometerplus.fbreader.network.atom.ATOMUpdated;
|
||||||
import org.geometerplus.fbreader.network.authentication.NetworkAuthenticationManager;
|
import org.geometerplus.fbreader.network.authentication.NetworkAuthenticationManager;
|
||||||
import org.geometerplus.fbreader.network.authentication.litres.LitResAuthenticationManager;
|
import org.geometerplus.fbreader.network.authentication.litres.LitResAuthenticationManager;
|
||||||
|
|
||||||
|
@ -45,8 +46,12 @@ public class OPDSLinkXMLReader extends OPDSXMLReader {
|
||||||
private final LinkedList<URLRewritingRule> myUrlRewritingRules = new LinkedList<URLRewritingRule>();
|
private final LinkedList<URLRewritingRule> myUrlRewritingRules = new LinkedList<URLRewritingRule>();
|
||||||
private HashMap<RelationAlias, String> myRelationAliases = new HashMap<RelationAlias, String>();
|
private HashMap<RelationAlias, String> myRelationAliases = new HashMap<RelationAlias, String>();
|
||||||
|
|
||||||
public LinkReader(NetworkLibrary.OnNewLinkListener listener) {
|
private ATOMUpdated myUpdatedTime;
|
||||||
|
private ATOMUpdated myReadAfterTime;
|
||||||
|
|
||||||
|
public LinkReader(NetworkLibrary.OnNewLinkListener listener, ATOMUpdated readAfter) {
|
||||||
myListener = listener;
|
myListener = listener;
|
||||||
|
myReadAfterTime = readAfter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAuthenticationType(String type) {
|
public void setAuthenticationType(String type) {
|
||||||
|
@ -72,6 +77,10 @@ public class OPDSLinkXMLReader extends OPDSXMLReader {
|
||||||
myRelationAliases.clear();
|
myRelationAliases.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ATOMUpdated getUpdatedTime() {
|
||||||
|
return myUpdatedTime;
|
||||||
|
}
|
||||||
|
|
||||||
private static final String ENTRY_ID_PREFIX = "urn:fbreader-org-catalog:";
|
private static final String ENTRY_ID_PREFIX = "urn:fbreader-org-catalog:";
|
||||||
|
|
||||||
public boolean processFeedEntry(OPDSEntry entry) {
|
public boolean processFeedEntry(OPDSEntry entry) {
|
||||||
|
@ -185,13 +194,33 @@ public class OPDSLinkXMLReader extends OPDSXMLReader {
|
||||||
return opdsLink;
|
return opdsLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean processFeedMetadata(OPDSFeedMetadata feed, boolean beforeEntries) { return false; }
|
public boolean processFeedMetadata(OPDSFeedMetadata feed, boolean beforeEntries) {
|
||||||
public void processFeedStart() {}
|
myUpdatedTime = feed.Updated;
|
||||||
public void processFeedEnd() {}
|
if (myUpdatedTime != null && myReadAfterTime != null
|
||||||
|
&& myUpdatedTime.compareTo(myReadAfterTime) <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return myListener == null; // no listener -- no need to proceed
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processFeedStart() {
|
||||||
|
myUpdatedTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void processFeedEnd() {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OPDSLinkXMLReader(NetworkLibrary.OnNewLinkListener listener) {
|
public OPDSLinkXMLReader() {
|
||||||
super(new LinkReader(listener));
|
super(new LinkReader(null, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OPDSLinkXMLReader(NetworkLibrary.OnNewLinkListener listener, ATOMUpdated readAfter) {
|
||||||
|
super(new LinkReader(listener, readAfter));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ATOMUpdated getUpdatedTime() {
|
||||||
|
return ((LinkReader) myFeedReader).getUpdatedTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String myFBReaderNamespaceId;
|
private String myFBReaderNamespaceId;
|
||||||
|
|
|
@ -196,4 +196,30 @@ public class ZLNetworkManager {
|
||||||
}
|
}
|
||||||
return message.toString();
|
return message.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public final String downloadToFile(String url, final File outFile) {
|
||||||
|
return downloadToFile(url, outFile, 8192);
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String downloadToFile(String url, final File outFile, final int bufferSize) {
|
||||||
|
return perform(new ZLNetworkRequest(url) {
|
||||||
|
public String handleStream(URLConnection connection, InputStream inputStream) throws IOException {
|
||||||
|
OutputStream outStream = new FileOutputStream(outFile);
|
||||||
|
try {
|
||||||
|
final byte[] buffer = new byte[bufferSize];
|
||||||
|
while (true) {
|
||||||
|
final int size = inputStream.read(buffer);
|
||||||
|
if (size <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
outStream.write(buffer, 0, size);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
outStream.close();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue