package com.gettingmobile.google.reader;
import android.util.Log;
import com.gettingmobile.Security.Hash;
import com.gettingmobile.goodnews.storage.StorageProvider;
import com.gettingmobile.io.Base16;
import com.gettingmobile.io.Base64;
import com.gettingmobile.io.CharacterSet;
import com.gettingmobile.io.IOUtils;
import com.gettingmobile.text.HtmlUtil;
import com.gettingmobile.text.Teaser;
import java.io.*;
import java.util.Date;
import java.util.Set;
import java.util.TreeSet;
/**
* An item represents a single entry in a feed. This class encapsulates basic information about a feed item without
* it's content.
*
* @author sven.wiegand
*/
public final class Item extends Element implements TaggedElement {
private static final String LOG_TAG = "goodnews.Item";
public static final String STORAGE_CATEGORY = "content";
public static final String FILE_NAME_SUMMARY = "summary";
public static final String FILE_NAME_CONTENT = "content";
public static final int MAX_TEASER_WORDS = 100;
public static final int MAX_DB_SIZE = 1024 * 1024; // 1 MB
private ElementId feedId = null;
private String feedTitle = null;
private Date timestamp = null;
private boolean read = false;
private Set<ElementId> tags = new TreeSet<ElementId>();
private Resource alternate = null;
private String author = null;
private boolean hasSummary = false;
private String summary = null;
private boolean hasContent = false;
private String content = null;
private boolean isExternalContent = false;
private boolean hasImages = false;
private String teaser = null;
private String directoryName = null;
public Item() {
}
@Override
public boolean isRead() {
return read;
}
public void setRead(boolean read) {
this.read = read;
}
public ElementId getFeedId() {
return feedId;
}
public void setFeedId(ElementId feedId) {
assert feedId.getType() == ElementType.FEED;
this.feedId = feedId;
}
public String getFeedTitle() {
return feedTitle;
}
public void setFeedTitle(String feedTitle) {
this.feedTitle = feedTitle;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public Set<ElementId> getTagIds() {
return tags;
}
@Override
public boolean hasTag(ElementId tagId) {
return tags.contains(tagId);
}
public Resource getAlternate() {
return alternate;
}
public void setAlternate(Resource alternate) {
this.alternate = alternate;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
public boolean hasSummary() {
return hasSummary;
}
public void setHasSummary(boolean hasSummary) {
this.hasSummary = hasSummary;
}
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
hasSummary = !isEmpty(summary) && !summary.equals(getTitle());
}
public boolean canStoreSummaryInDb() {
return summary == null || summary.length() < MAX_DB_SIZE;
}
public boolean hasContent() {
return hasContent;
}
public void setHasContent(boolean hasContent) {
this.hasContent = hasContent;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
hasContent = !isEmpty(content) && !content.equals(getTitle()) && !content.equals(summary);
}
public boolean canStoreContentInDb() {
return content == null || content.length() < MAX_DB_SIZE;
}
public boolean isExternalContent() {
return isExternalContent;
}
public void setIsExternalContent(boolean externalContent) {
isExternalContent = externalContent;
}
public boolean hasImages() {
return hasImages;
}
public void setHasImages(boolean hasImages) {
this.hasImages = hasImages;
}
public boolean hasTeaser() {
return !isEmpty(teaser);
}
public String getTeaser() {
return teaser;
}
public String getTeaser(int maxWords) {
return teaser != null ? Teaser.tease(teaser, 0, maxWords, HtmlUtil.ELLIPSIS) : null;
}
public void setTeaser(String teaser) {
this.teaser = teaser;
}
public void createTeaser(ItemTeaserSource sourceType, int startChar, StorageProvider storageProvider) {
if (sourceType != ItemTeaserSource.NONE) {
try {
loadIfRequired(storageProvider);
final String source = (sourceType == ItemTeaserSource.PREFER_SUMMARY && hasSummary) ? summary : content;
if (source != null) {
teaser = Teaser.tease(HtmlUtil.toText(source), startChar, MAX_TEASER_WORDS, HtmlUtil.ELLIPSIS);
} else {
teaser = null;
}
} catch (IOException ex) {
teaser = null;
}
} else {
teaser = null;
}
}
public void updateContentFlags() {
setSummary(summary);
setContent(content);
}
public void processContentTreatment(ItemContentTreatment summaryTreatment, ItemContentTreatment contentTreatment) {
String newSummary = null;
String newContent = null;
switch (summaryTreatment) {
case IGNORE:
break;
case TREAT_AS_SUMMARY:
newSummary = summary;
break;
case TREAT_AS_CONTENT:
newContent = summary;
break;
}
switch (contentTreatment) {
case IGNORE:
break;
case TREAT_AS_SUMMARY:
newSummary = content;
break;
case TREAT_AS_CONTENT:
newContent = content;
break;
}
setSummary(newSummary);
setContent(newContent);
}
/*
* file system serialization
*/
public static String convertFromOldDirectoryName(String name) throws IOException {
final byte[] data = Base64.decode(name.replace('-', '/'));
return Base16.encode(data);
}
public static String convertToOldDirectoryName(String name) {
final byte[] data = Base16.decode(name);
return Base64.encode(data).replace('/', '-');
}
public static String getDirectoryName(ElementId itemId) {
return Base16.encode(Hash.getInstance().create(itemId.toString()));
}
public String getDirectoryName() {
if (directoryName == null) {
if (getId() == null || getId().getId().length() == 0)
throw new IllegalStateException("The item must be initialized before accessing file operations.");
directoryName = getDirectoryName(getId());
}
return directoryName;
}
public static File getDirectory(StorageProvider storageProvider, String dirName) {
return new File(storageProvider.getDirectory(STORAGE_CATEGORY),
"/" + dirName.charAt(0) + "/" + dirName.charAt(1) + "/" + dirName);
}
public File getDirectory(StorageProvider storageProvider) {
return getDirectory(storageProvider, getDirectoryName());
}
private File getOldDirectory(StorageProvider storageProvider, File dir) {
return new File(storageProvider.getDirectory(STORAGE_CATEGORY), convertToOldDirectoryName(dir.getName()));
}
private File getDirectoryWithFallback(StorageProvider storageProvider) {
final File dir = getDirectory(storageProvider);
return dir.exists() ? dir : getOldDirectory(storageProvider, dir);
}
protected void save(File dir, String fileName, String str) throws IOException {
final File file = new File(dir, fileName);
final Writer w = new BufferedWriter(new FileWriter(file));
try {
w.write(str);
} finally {
IOUtils.closeQuietly(w);
}
}
public void saveIfApplicable(boolean storeContentInFiles, StorageProvider storageProvider) throws IOException {
if (hasSummary || hasContent) {
final File dir = getDirectory(storageProvider);
if (summary != null && (storeContentInFiles || !canStoreSummaryInDb())) {
Log.i(LOG_TAG, "Storing item summary to " + dir.getAbsolutePath());
IOUtils.ensureDirExists(dir);
save(dir, FILE_NAME_SUMMARY, summary);
}
if (content != null && (storeContentInFiles || !canStoreContentInDb())) {
Log.i(LOG_TAG, "Storing item content to " + dir.getAbsolutePath());
IOUtils.ensureDirExists(dir);
save(dir, FILE_NAME_CONTENT, content);
}
}
}
public String load(File dir, String fileName) throws IOException {
final File f = new File(dir, fileName);
if (f.exists()) {
final BufferedReader r = new BufferedReader(new InputStreamReader(new FileInputStream(f), CharacterSet.UTF8));
try {
final StringBuilder sb = new StringBuilder();
for (int c = r.read(); c >= 0; c = r.read()) {
sb.append((char) c);
}
return sb.toString();
} finally {
IOUtils.closeQuietly(r);
}
} else {
return null;
}
}
public Item loadIfRequired(StorageProvider storageProvider) throws IOException {
if (storageProvider != null && ((hasSummary && isEmpty(summary)) || (hasContent && isEmpty(content)))) {
final File dir = getDirectoryWithFallback(storageProvider);
if (hasSummary && isEmpty(summary)) {
Log.d(LOG_TAG, "Loading item summary from " + dir.getAbsolutePath());
summary = load(dir, FILE_NAME_SUMMARY);
}
if (hasContent && isEmpty(content)) {
Log.d(LOG_TAG, "Loading item content from " + dir.getAbsolutePath());
content = load(dir, FILE_NAME_CONTENT);
}
}
return this;
}
/*
* helpers
*/
private static boolean isEmpty(String string) {
return string == null || string.length() == 0;
}
}