diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | legacystorage.c | 144 | ||||
-rw-r--r-- | legacystorage.h | 40 | ||||
-rw-r--r-- | osdteletext.c | 14 | ||||
-rw-r--r-- | packedstorage.c | 131 | ||||
-rw-r--r-- | packedstorage.h | 36 | ||||
-rw-r--r-- | pageid.h | 31 | ||||
-rw-r--r-- | rootdir.c | 21 | ||||
-rw-r--r-- | rootdir.h | 22 | ||||
-rw-r--r-- | storage.c | 142 | ||||
-rw-r--r-- | storage.h | 65 | ||||
-rw-r--r-- | txtrecv.c | 409 | ||||
-rw-r--r-- | txtrecv.h | 110 |
13 files changed, 646 insertions, 521 deletions
@@ -53,7 +53,7 @@ DEFINES += -DPLUGIN_NAME_I18N='"$(PLUGIN)"' ### The object files (add further files here): -OBJS = $(PLUGIN).o menu.o txtfont.o txtrecv.o txtrender.o displaybase.o display.o +OBJS = $(PLUGIN).o menu.o txtfont.o txtrecv.o txtrender.o displaybase.o display.o storage.o legacystorage.o packedstorage.o rootdir.o ### The main target: diff --git a/legacystorage.c b/legacystorage.c new file mode 100644 index 0000000..3bed512 --- /dev/null +++ b/legacystorage.c @@ -0,0 +1,144 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#include <sys/vfs.h> + +#include "legacystorage.h" + +LegacyStorage::LegacyStorage(int maxMB) + : fsBlockSize(1), pageBytes(TELETEXT_PAGESIZE) +{ + initMaxStorage(maxMB); +} + +LegacyStorage::~LegacyStorage() { +} + +/* +static inline int FilesForMegabytes(double MB, int blocksize) { + double pageBytes; + if (TELETEXT_PAGESIZE<=blocksize) + pageBytes=blocksize; + else + pageBytes=((TELETEXT_PAGESIZE/blocksize)+1)*blocksize; + //reserve 10% for directories + return (int)( (1024.0 * 1024.0 * (MB-MB*0.1)) / pageBytes ); +}*/ + +int LegacyStorage::actualFileSize(int netFileSize) { + if (netFileSize<=0) + return 0; + if (netFileSize<=fsBlockSize) + return fsBlockSize; + else + return ((netFileSize/fsBlockSize)+1)*fsBlockSize; +} + +//max==0 means unlimited, max==-1 means a reasonable default value shall be calculated +void LegacyStorage::initMaxStorage(int maxMB) { + struct statfs fs; + if (statfs(getRootDir(), &fs)!=0) { + esyslog("OSD-Teletext: Error statfs'ing root directory \"%s\": %s, cache size uncontrolled", getRootDir(), strerror(errno)); + return; + } + fsBlockSize=fs.f_bsize; + + pageBytes=actualFileSize(TELETEXT_PAGESIZE); + + if (maxMB>=0) { + if (maxMB<3) { + esyslog("OSD-Teletext: Request to use at most %d MB for caching. This is not enough, using 3 MB", maxMB); + maxMB=3; + } + maxBytes=MEGABYTE(maxMB); + } else if (maxMB==-1) { + //calculate a default value + double blocksPerMeg = 1024.0 * 1024.0 / fs.f_bsize; + double capacityMB=fs.f_blocks / blocksPerMeg; + double freeMB=(fs.f_bavail / blocksPerMeg); + if (capacityMB<=50 || freeMB<50) { + //small (<=50MB) filesystems as root dir are assumed to be dedicated for use as a teletext cache + //for others, the maximum default size is set to 50 MB + maxBytes=MEGABYTE((int)freeMB); + //maxPages= FilesForMegabytes(freeMB, fs.f_bsize); + if (freeMB<3.0) { + esyslog("OSD-Teletext: Less than %.1f MB free on filesystem of root directory \"%s\"!", freeMB, getRootDir()); + maxBytes=MEGABYTE(3); + } + } else { + //the maximum default size is set to 50 MB + maxBytes=MEGABYTE(50); + } + //printf("Set maxBytes to %ld, %.2f %.2f\n", maxBytes, capacityMB, freeMB); + } +} + +void LegacyStorage::cleanUp() { + byteCount -= Storage::doCleanUp(); +} + +void LegacyStorage::registerFile(PageID page) { + //pageBytes is already effective size + if ( maxBytes && (byteCount+=pageBytes)>maxBytes ) + freeSpace(); +} + +StorageHandle LegacyStorage::openForReading(PageID page, bool countAsAccess) { + //the countAsAccess argument was intended for use in a LRU cache, currently unused + char filename[PATH_MAX]; + getFilename(filename, sizeof(filename), page); + StorageHandle ret=(StorageHandle)open(filename, O_RDONLY); + return ret; +} + +StorageHandle LegacyStorage::openForWriting(PageID page) { + static bool wroteError=false; + char filename[PATH_MAX]; + getFilename(filename, sizeof(filename), page); + bool existed=exists(filename); + //first try + StorageHandle fd=(StorageHandle)open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (fd) { + if (!existed) + registerFile(page); + return fd; + } + //no space on disk? make some space available + if (errno == ENOSPC) + freeSpace(); + //second try + fd=(StorageHandle)open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (!fd && !wroteError) { + //report error to syslog - once! + wroteError=true; + esyslog("OSD-Teletext: Error opening teletext file %s: %s", filename, strerror(errno)); + } + //make sure newly created files are counted + if (fd && !existed) + registerFile(page); + return fd; +} + +ssize_t LegacyStorage::write(const void *ptr, size_t size, StorageHandle stream) { + ssize_t written; + if (!(written=::write((int)stream, ptr, size)) ) { + switch (errno) { + case ENOSPC: + freeSpace(); + return ::write((int)stream, ptr, size); + case EINTR: + esyslog("OSD-Teletext: EINTR while writing. Please contact the author and tell him this happened."); + break; + default: + break; + } + } + return written; +} diff --git a/legacystorage.h b/legacystorage.h new file mode 100644 index 0000000..266a6fd --- /dev/null +++ b/legacystorage.h @@ -0,0 +1,40 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __LEGACYSTORAGE_H +#define __LEGACYSTORAGE_H + +#include "storage.h" + +class LegacyStorage : public Storage { +private: + void initMaxStorage(int maxMB=-1); +public: + LegacyStorage(int maxMB); + virtual ~LegacyStorage(); + virtual void cleanUp(); + + virtual StorageHandle openForWriting(PageID page); + virtual StorageHandle openForReading(PageID page, bool countAsAccess); + virtual ssize_t write(const void *ptr, size_t size, StorageHandle stream); + virtual ssize_t read(void *ptr, size_t size, StorageHandle stream) + { return ::read((int)stream, ptr, size); } + virtual void close(StorageHandle stream) + { ::close((int)stream); } +protected: + void registerFile(PageID page); + virtual int actualFileSize(int netFileSize); + //int maxPages; + long maxBytes; + int fsBlockSize; + int pageBytes; +}; + +#endif diff --git a/osdteletext.c b/osdteletext.c index 04e0b49..f03ef54 100644 --- a/osdteletext.c +++ b/osdteletext.c @@ -20,6 +20,8 @@ using namespace std; #include "menu.h" #include "txtrecv.h" #include "setup.h" +#include "legacystorage.h" +#include "packedstorage.h" #if defined(APIVERSNUM) && APIVERSNUM < 10739 #error "VDR-1.7.39 API version or greater is required!" @@ -175,10 +177,16 @@ bool cPluginTeletextosd::ProcessArgs(int argc, char *argv[]) bool cPluginTeletextosd::Start(void) { - // Start any background activities the plugin shall perform. - //Clean any files which might be remaining from the last session, + // Start any background activities the plugin shall perform. + //Clean any files which might be remaining from the last session, //perhaps due to a crash they have not been deleted. - storage = Storage::CreateInstance(storageSystem, maxStorage); + switch (storageSystem) { + case Storage::StorageSystemLegacy: + storage = new LegacyStorage(maxStorage); + case Storage::StorageSystemPacked: + default: + storage = new PackedStorage(maxStorage); + } initTexts(); if (startReceiver) diff --git a/packedstorage.c b/packedstorage.c new file mode 100644 index 0000000..f23866f --- /dev/null +++ b/packedstorage.c @@ -0,0 +1,131 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#include "packedstorage.h" + +PackedStorage::PackedStorage(int maxMB) + : LegacyStorage(maxMB) { +} +#define TOC_SIZE 8 +//The file structure is simple: +// TOC_SIZE*PageAddress contains the numbers of the following pages +// TOC_SIZE*TELETEXT_PAGESIZE contains the page data +//and the same again. +bool PackedStorage::seekTo(PageID page, int desc, bool create) { + lseek(desc, 0, SEEK_SET); + PageAddress addr[TOC_SIZE]; + + while (::read(desc, addr, sizeof(addr)) == sizeof(addr)) { + lseek(desc, 0, SEEK_CUR); + for (int index=0; index<TOC_SIZE; index++) { + if (addr[index]==page) { + lseek(desc, index*TELETEXT_PAGESIZE, SEEK_CUR); + return true; + } else if (addr[index].page==0) { + //0 means: no more pages follow + if (create) { + //rewind what was read + lseek(desc, -(off_t)sizeof(addr), SEEK_CUR); + //update index + addr[index]=page; + if (::write(desc, addr, sizeof(addr)) != sizeof(addr)) + return false; + //seek to data position + lseek(desc, TELETEXT_PAGESIZE*index, SEEK_CUR); + return true; + } else + return false; + } + } + + //seek over data area + lseek(desc, TELETEXT_PAGESIZE*TOC_SIZE, SEEK_CUR); + } + + int oldSize=actualFileSize(lseek(desc, 0, SEEK_CUR)); + if (create) { + //create a new set of a TOC and a TOC_SIZE*TELETEXT_PAGESIZE data area + memset(addr, 0, sizeof(addr)); + //first entry is our page + addr[0]=page; + if (::write(desc, addr, sizeof(addr)) != sizeof(addr)) + return false; + //seek beyond end of file + lseek(desc, (TELETEXT_PAGESIZE*TOC_SIZE)-1, SEEK_CUR); + //write one byte to enlarge the file to the sought position + char c=1; + if (::write(desc, &c, 1) != 1) + return false; + //Now, calculate new file size + byteCount += ( actualFileSize(lseek(desc, 0, SEEK_CUR)) - oldSize ); + //seek to beginning of data, which is requested + lseek(desc, -(off_t)(TELETEXT_PAGESIZE*TOC_SIZE), SEEK_CUR); + return true; + } else + return false; +} + +void PackedStorage::getFilename(char *buffer, int bufLength, PageID page) { + //This is a different scheme: page 576_07 will have the name 570s.vtx, the same as e.g. 571_01 or 575_00 + //Think of "the five hundred seventies" + snprintf(buffer, bufLength, "%s/%s/%03xs.vtx", getRootDir(), + *page.channel.ToString(), (page.page & 0xFF0)); +} + +StorageHandle PackedStorage::openForWriting(PageID page) { + static bool wroteError=false; + char filename[PATH_MAX]; + getFilename(filename, sizeof(filename), page); + //first try + int desc=open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (desc != -1) { + if (!seekTo(page, desc, true)) { + ::close(desc); + return StorageHandle(); + } + if ( maxBytes && byteCount>maxBytes ) + freeSpace(); + return (StorageHandle)desc; + } + //no space on disk? make some space available + if (errno == ENOSPC) + freeSpace(); + //second try + desc=open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (desc==-1 && !wroteError) { + //report error to syslog - once! + wroteError=true; + esyslog("OSD-Teletext: Error opening teletext file %s: %s", filename, strerror(errno)); + } + + if (desc==-1) + return StorageHandle(); + else if (!seekTo(page, desc, true)) { + ::close(desc); + return StorageHandle(); + } + + if ( maxBytes && byteCount>maxBytes ) + freeSpace(); + return (StorageHandle)desc; +} + +StorageHandle PackedStorage::openForReading(PageID page, bool countAsAccess) { + int desc; + if ( (desc=(int)LegacyStorage::openForReading(page, false))!= -1 ) { + if (!seekTo(page, desc, false)) { + //this is not an error condition here, may and shall happen! + ::close(desc); + } else { + return (StorageHandle)desc; + } + } + return StorageHandle(); +} diff --git a/packedstorage.h b/packedstorage.h new file mode 100644 index 0000000..5df219b --- /dev/null +++ b/packedstorage.h @@ -0,0 +1,36 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __PACKEDSTORAGE_H +#define __PACKEDSTORAGE_H + +#include "legacystorage.h" + +class PackedStorage : public LegacyStorage { +public: + PackedStorage(int maxMB); + + virtual void getFilename(char *buffer, int bufLength, PageID page); + virtual StorageHandle openForWriting(PageID page); + virtual StorageHandle openForReading(PageID page, bool countAsAccess); +protected: + struct PageAddress { + bool operator==(const PageID &id) const + { return page==id.page && subPage==id.subPage; } + void operator=(const PageID &id) + { page=id.page; subPage=id.subPage; } + int page; + int subPage; + }; + bool seekTo(PageID page, int fd, bool create); + void registerFile(PageID page); +}; + +#endif diff --git a/pageid.h b/pageid.h new file mode 100644 index 0000000..cb2709d --- /dev/null +++ b/pageid.h @@ -0,0 +1,31 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __PAGEID_H +#define __PAGEID_H + +#include <vdr/status.h> +#include <vdr/receiver.h> +#include <vdr/thread.h> +#include <vdr/ringbuffer.h> + +#include <stdio.h> +#include <unistd.h> + +struct PageID { + PageID() { page=subPage=0; } + PageID(tChannelID id, int p, int s) { set(id, p, s); } + void set(tChannelID id, int p, int s) { channel=id; page=p; subPage=s; } + tChannelID channel; + int page; + int subPage; +}; + +#endif diff --git a/rootdir.c b/rootdir.c new file mode 100644 index 0000000..7076e0d --- /dev/null +++ b/rootdir.c @@ -0,0 +1,21 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#include "rootdir.h" + +const char *RootDir::root = "/var/cache/vdr/vtx"; + +void RootDir::setRootDir(const char *newRoot) { + root=newRoot; +} + +const char *RootDir::getRootDir() { + return root; +} diff --git a/rootdir.h b/rootdir.h new file mode 100644 index 0000000..90ce49c --- /dev/null +++ b/rootdir.h @@ -0,0 +1,22 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __ROOTDIR_H +#define __ROOTDIR_H + +class RootDir { +public: + static void setRootDir(const char *); + static const char *getRootDir(); +protected: + static const char *root; +}; + +#endif diff --git a/storage.c b/storage.c new file mode 100644 index 0000000..10bd74a --- /dev/null +++ b/storage.c @@ -0,0 +1,142 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#include "storage.h" + +int Storage::doCleanUp() { + DIR *top=opendir(root); + int pagesDeleted=0; + if (top) { + struct dirent *chandir, path; + struct stat chandirstat; + char fullPath[PATH_MAX]; + while ( (!readdir_r(top, &path, &chandir) && chandir != NULL) ) { + if (strcmp(chandir->d_name, "..")==0 || strcmp(chandir->d_name, ".")==0) + continue; + snprintf(fullPath, PATH_MAX, "%s/%s", root, chandir->d_name); + if (stat(fullPath, &chandirstat)==0) { + if (S_ISDIR(chandirstat.st_mode)) { + pagesDeleted+=cleanSubDir(fullPath); + } + } + } + closedir(top); + } else { + esyslog("OSD-Teletext: Error opening teletext storage directory \"%s\": %s", root, strerror(errno)); + } + return pagesDeleted; +} + +int Storage::cleanSubDir(const char *dir) { + static bool reportedError=false; //avoid filling up syslog + DIR *d=opendir(dir); + bool hadError=false; + int bytesDeleted=0; + if (d) { + struct dirent *txtfile, path; + struct stat txtfilestat; + char fullPath[PATH_MAX]; + int filesize; + while ( (!readdir_r(d, &path, &txtfile) && txtfile != NULL) ) { + int len=strlen(txtfile->d_name); + //check that the file end with .vtx to avoid accidents and disasters + if (strcmp(txtfile->d_name+len-4, ".vtx")==0) { + snprintf(fullPath, PATH_MAX, "%s/%s", dir, txtfile->d_name); + stat(fullPath, &txtfilestat); + filesize=actualFileSize(txtfilestat.st_size); + int ret=unlink(fullPath); + if (ret==0) + bytesDeleted+=filesize; + else + hadError=ret; + } + } + closedir(d); + rmdir(dir); + } else { + if (!reportedError) { + esyslog("OSD-Teletext: Error opening teletext storage subdirectory \"%s\": %s", dir, strerror(errno)); + reportedError=true; + } + } + + if (hadError && !reportedError) { + esyslog("OSD-Teletext: Error removing teletext storage subdirectory \"%s\": %s", dir, strerror(hadError)); + reportedError=true; + } + return bytesDeleted; +} + +Storage::Storage() + : byteCount(0), failedFreeSpace(false) +{ +} + +Storage::~Storage() { +} + +void Storage::freeSpace() { + //there might be a situation where only the current directory is left and + //occupies the whole space. We cannot delete anything. Don't waste time scanning. + if (failedFreeSpace) + return; + + //printf("freeSpace()\n"); + time_t min=time(0); + char minDir[PATH_MAX]; + char fullPath[PATH_MAX]; + DIR *top=opendir(getRootDir()); + if (top) { + int haveDir=0; + struct dirent *chandir, path; + struct stat chandirstat; + while ( (!readdir_r(top, &path, &chandir) && chandir != NULL) ) { + if (strcmp(chandir->d_name, "..")==0 || strcmp(chandir->d_name, ".")==0) + continue; + snprintf(fullPath, PATH_MAX, "%s/%s", getRootDir(), chandir->d_name); + if (stat(fullPath, &chandirstat)==0) { + if (S_ISDIR(chandirstat.st_mode)) { + if (chandirstat.st_ctime < min && strcmp(fullPath, currentDir)) { + min=chandirstat.st_ctime; + strcpy(minDir, fullPath); + haveDir++; + } + } + } + } + closedir(top); + + //if haveDir, only current directory present, which must not be deleted + if (haveDir>=2) + byteCount-=cleanSubDir(minDir); + else + failedFreeSpace=true; + } +} + +bool Storage::exists(const char* file) { + struct stat s; + return (stat(file, &s)==0); +} + +void Storage::getFilename(char *buffer, int bufLength, PageID page) { + snprintf(buffer, bufLength, "%s/%s/%03x_%02x.vtx", getRootDir(), + *page.channel.ToString(), page.page, page.subPage); +} + +void Storage::prepareDirectory(tChannelID chan) { + currentDir = cString::sprintf("%s/%s", root, *chan.ToString()); + if (!MakeDirs(currentDir, 1)) { + esyslog("OSD-Teletext: Error preparing directory for channel \"%s\"", + *chan.ToString()); + return; + } + failedFreeSpace=false; +} diff --git a/storage.h b/storage.h new file mode 100644 index 0000000..29ae022 --- /dev/null +++ b/storage.h @@ -0,0 +1,65 @@ +/*************************************************************** -*- c++ -*- + * Copyright (c) 2003,2004 by Marcel Wiesweg * + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef __STORAGE_H +#define __STORAGE_H + +#include "rootdir.h" +#include "pageid.h" + +#define TELETEXT_PAGESIZE 972 + +struct StorageHandle { +public: + StorageHandle() { handle=-1; } + StorageHandle(const StorageHandle &s) { handle=s.handle; } + StorageHandle(int h) { handle=h; } + StorageHandle &operator=(int h) { handle=h; return *this; } + StorageHandle &operator=(const StorageHandle &s) { handle=s.handle; return *this; } + operator bool() const { return handle!=-1; } + operator int() const { return handle; } +private: + int handle; +}; + +enum StorageSystem { StorageSystemLegacy, StorageSystemPacked }; + +class Storage : public RootDir { +public: + virtual ~Storage(); + enum StorageSystem { StorageSystemLegacy, StorageSystemPacked }; + //must be called before the first call to instance() + + //must be called before operation starts. Set all options (RootDir, maxStorage) before. + virtual void cleanUp() = 0; + + virtual void getFilename(char *buffer, int bufLength, PageID page); + virtual void prepareDirectory(tChannelID chan); + + virtual StorageHandle openForWriting(PageID page) = 0; + virtual StorageHandle openForReading(PageID page, bool countAsAccess) = 0; + virtual ssize_t write(const void *ptr, size_t size, StorageHandle stream) = 0; + virtual ssize_t read(void *ptr, size_t size, StorageHandle stream) = 0; + virtual void close(StorageHandle stream) = 0; +protected: + Storage(); + int cleanSubDir(const char *dir); + int doCleanUp(); + virtual int actualFileSize(int netFileSize) { return netFileSize; } + void freeSpace(); + bool exists(const char* file); + + long byteCount; + cString currentDir; +private: + bool failedFreeSpace; +}; + +#endif @@ -27,415 +27,6 @@ #include <sys/stat.h> #include <fcntl.h> -const char *RootDir::root = "/var/cache/vdr/vtx"; - -void RootDir::setRootDir(const char *newRoot) { - root=newRoot; -} - -const char *RootDir::getRootDir() { - return root; -} - -int Storage::doCleanUp() { - DIR *top=opendir(root); - int pagesDeleted=0; - if (top) { - struct dirent *chandir, path; - struct stat chandirstat; - char fullPath[PATH_MAX]; - while ( (!readdir_r(top, &path, &chandir) && chandir != NULL) ) { - if (strcmp(chandir->d_name, "..")==0 || strcmp(chandir->d_name, ".")==0) - continue; - snprintf(fullPath, PATH_MAX, "%s/%s", root, chandir->d_name); - if (stat(fullPath, &chandirstat)==0) { - if (S_ISDIR(chandirstat.st_mode)) { - pagesDeleted+=cleanSubDir(fullPath); - } - } - } - closedir(top); - } else { - esyslog("OSD-Teletext: Error opening teletext storage directory \"%s\": %s", root, strerror(errno)); - } - return pagesDeleted; -} - -int Storage::cleanSubDir(const char *dir) { - static bool reportedError=false; //avoid filling up syslog - DIR *d=opendir(dir); - bool hadError=false; - int bytesDeleted=0; - if (d) { - struct dirent *txtfile, path; - struct stat txtfilestat; - char fullPath[PATH_MAX]; - int filesize; - while ( (!readdir_r(d, &path, &txtfile) && txtfile != NULL) ) { - int len=strlen(txtfile->d_name); - //check that the file end with .vtx to avoid accidents and disasters - if (strcmp(txtfile->d_name+len-4, ".vtx")==0) { - snprintf(fullPath, PATH_MAX, "%s/%s", dir, txtfile->d_name); - stat(fullPath, &txtfilestat); - filesize=actualFileSize(txtfilestat.st_size); - int ret=unlink(fullPath); - if (ret==0) - bytesDeleted+=filesize; - else - hadError=ret; - } - } - closedir(d); - rmdir(dir); - } else { - if (!reportedError) { - esyslog("OSD-Teletext: Error opening teletext storage subdirectory \"%s\": %s", dir, strerror(errno)); - reportedError=true; - } - } - - if (hadError && !reportedError) { - esyslog("OSD-Teletext: Error removing teletext storage subdirectory \"%s\": %s", dir, strerror(hadError)); - reportedError=true; - } - return bytesDeleted; -} - -Storage::Storage() - : byteCount(0), failedFreeSpace(false) -{ -} - -Storage::~Storage() { -} - -Storage *Storage::CreateInstance(StorageSystem system, int storageLimit) { - switch (system) { - case StorageSystemLegacy: - return new LegacyStorage(storageLimit); - case StorageSystemPacked: - default: - return new PackedStorage(storageLimit); - } -} - -void Storage::freeSpace() { - //there might be a situation where only the current directory is left and - //occupies the whole space. We cannot delete anything. Don't waste time scanning. - if (failedFreeSpace) - return; - - //printf("freeSpace()\n"); - time_t min=time(0); - char minDir[PATH_MAX]; - char fullPath[PATH_MAX]; - DIR *top=opendir(getRootDir()); - if (top) { - int haveDir=0; - struct dirent *chandir, path; - struct stat chandirstat; - while ( (!readdir_r(top, &path, &chandir) && chandir != NULL) ) { - if (strcmp(chandir->d_name, "..")==0 || strcmp(chandir->d_name, ".")==0) - continue; - snprintf(fullPath, PATH_MAX, "%s/%s", getRootDir(), chandir->d_name); - if (stat(fullPath, &chandirstat)==0) { - if (S_ISDIR(chandirstat.st_mode)) { - if (chandirstat.st_ctime < min && strcmp(fullPath, currentDir)) { - min=chandirstat.st_ctime; - strcpy(minDir, fullPath); - haveDir++; - } - } - } - } - closedir(top); - - //if haveDir, only current directory present, which must not be deleted - if (haveDir>=2) - byteCount-=cleanSubDir(minDir); - else - failedFreeSpace=true; - } -} - -bool Storage::exists(const char* file) { - struct stat s; - return (stat(file, &s)==0); -} - -void Storage::getFilename(char *buffer, int bufLength, PageID page) { - snprintf(buffer, bufLength, "%s/%s/%03x_%02x.vtx", getRootDir(), - *page.channel.ToString(), page.page, page.subPage); -} - -void Storage::prepareDirectory(tChannelID chan) { - currentDir = cString::sprintf("%s/%s", root, *chan.ToString()); - if (!MakeDirs(currentDir, 1)) { - esyslog("OSD-Teletext: Error preparing directory for channel \"%s\"", - *chan.ToString()); - return; - } - failedFreeSpace=false; -} - -#define TELETEXT_PAGESIZE 972 - -LegacyStorage::LegacyStorage(int maxMB) - : fsBlockSize(1), pageBytes(TELETEXT_PAGESIZE) -{ - initMaxStorage(maxMB); -} - -LegacyStorage::~LegacyStorage() { -} - -/* -static inline int FilesForMegabytes(double MB, int blocksize) { - double pageBytes; - if (TELETEXT_PAGESIZE<=blocksize) - pageBytes=blocksize; - else - pageBytes=((TELETEXT_PAGESIZE/blocksize)+1)*blocksize; - //reserve 10% for directories - return (int)( (1024.0 * 1024.0 * (MB-MB*0.1)) / pageBytes ); -}*/ - -int LegacyStorage::actualFileSize(int netFileSize) { - if (netFileSize<=0) - return 0; - if (netFileSize<=fsBlockSize) - return fsBlockSize; - else - return ((netFileSize/fsBlockSize)+1)*fsBlockSize; -} - -//max==0 means unlimited, max==-1 means a reasonable default value shall be calculated -void LegacyStorage::initMaxStorage(int maxMB) { - struct statfs fs; - if (statfs(getRootDir(), &fs)!=0) { - esyslog("OSD-Teletext: Error statfs'ing root directory \"%s\": %s, cache size uncontrolled", getRootDir(), strerror(errno)); - return; - } - fsBlockSize=fs.f_bsize; - - pageBytes=actualFileSize(TELETEXT_PAGESIZE); - - if (maxMB>=0) { - if (maxMB<3) { - esyslog("OSD-Teletext: Request to use at most %d MB for caching. This is not enough, using 3 MB", maxMB); - maxMB=3; - } - maxBytes=MEGABYTE(maxMB); - } else if (maxMB==-1) { - //calculate a default value - double blocksPerMeg = 1024.0 * 1024.0 / fs.f_bsize; - double capacityMB=fs.f_blocks / blocksPerMeg; - double freeMB=(fs.f_bavail / blocksPerMeg); - if (capacityMB<=50 || freeMB<50) { - //small (<=50MB) filesystems as root dir are assumed to be dedicated for use as a teletext cache - //for others, the maximum default size is set to 50 MB - maxBytes=MEGABYTE((int)freeMB); - //maxPages= FilesForMegabytes(freeMB, fs.f_bsize); - if (freeMB<3.0) { - esyslog("OSD-Teletext: Less than %.1f MB free on filesystem of root directory \"%s\"!", freeMB, getRootDir()); - maxBytes=MEGABYTE(3); - } - } else { - //the maximum default size is set to 50 MB - maxBytes=MEGABYTE(50); - } - //printf("Set maxBytes to %ld, %.2f %.2f\n", maxBytes, capacityMB, freeMB); - } -} - -void LegacyStorage::cleanUp() { - byteCount -= Storage::doCleanUp(); -} - -void LegacyStorage::registerFile(PageID page) { - //pageBytes is already effective size - if ( maxBytes && (byteCount+=pageBytes)>maxBytes ) - freeSpace(); -} - -StorageHandle LegacyStorage::openForReading(PageID page, bool countAsAccess) { - //the countAsAccess argument was intended for use in a LRU cache, currently unused - char filename[PATH_MAX]; - getFilename(filename, sizeof(filename), page); - StorageHandle ret=(StorageHandle)open(filename, O_RDONLY); - return ret; -} - -StorageHandle LegacyStorage::openForWriting(PageID page) { - static bool wroteError=false; - char filename[PATH_MAX]; - getFilename(filename, sizeof(filename), page); - bool existed=exists(filename); - //first try - StorageHandle fd=(StorageHandle)open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (fd) { - if (!existed) - registerFile(page); - return fd; - } - //no space on disk? make some space available - if (errno == ENOSPC) - freeSpace(); - //second try - fd=(StorageHandle)open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (!fd && !wroteError) { - //report error to syslog - once! - wroteError=true; - esyslog("OSD-Teletext: Error opening teletext file %s: %s", filename, strerror(errno)); - } - //make sure newly created files are counted - if (fd && !existed) - registerFile(page); - return fd; -} - -ssize_t LegacyStorage::write(const void *ptr, size_t size, StorageHandle stream) { - ssize_t written; - if (!(written=::write((int)stream, ptr, size)) ) { - switch (errno) { - case ENOSPC: - freeSpace(); - return ::write((int)stream, ptr, size); - case EINTR: - esyslog("OSD-Teletext: EINTR while writing. Please contact the author and tell him this happened."); - break; - default: - break; - } - } - return written; -} - - - -PackedStorage::PackedStorage(int maxMB) - : LegacyStorage(maxMB) { -} -#define TOC_SIZE 8 -//The file structure is simple: -// TOC_SIZE*PageAddress contains the numbers of the following pages -// TOC_SIZE*TELETEXT_PAGESIZE contains the page data -//and the same again. -bool PackedStorage::seekTo(PageID page, int desc, bool create) { - lseek(desc, 0, SEEK_SET); - PageAddress addr[TOC_SIZE]; - - while (::read(desc, addr, sizeof(addr)) == sizeof(addr)) { - lseek(desc, 0, SEEK_CUR); - for (int index=0; index<TOC_SIZE; index++) { - if (addr[index]==page) { - lseek(desc, index*TELETEXT_PAGESIZE, SEEK_CUR); - return true; - } else if (addr[index].page==0) { - //0 means: no more pages follow - if (create) { - //rewind what was read - lseek(desc, -(off_t)sizeof(addr), SEEK_CUR); - //update index - addr[index]=page; - if (::write(desc, addr, sizeof(addr)) != sizeof(addr)) - return false; - //seek to data position - lseek(desc, TELETEXT_PAGESIZE*index, SEEK_CUR); - return true; - } else - return false; - } - } - - //seek over data area - lseek(desc, TELETEXT_PAGESIZE*TOC_SIZE, SEEK_CUR); - } - - int oldSize=actualFileSize(lseek(desc, 0, SEEK_CUR)); - if (create) { - //create a new set of a TOC and a TOC_SIZE*TELETEXT_PAGESIZE data area - memset(addr, 0, sizeof(addr)); - //first entry is our page - addr[0]=page; - if (::write(desc, addr, sizeof(addr)) != sizeof(addr)) - return false; - //seek beyond end of file - lseek(desc, (TELETEXT_PAGESIZE*TOC_SIZE)-1, SEEK_CUR); - //write one byte to enlarge the file to the sought position - char c=1; - if (::write(desc, &c, 1) != 1) - return false; - //Now, calculate new file size - byteCount += ( actualFileSize(lseek(desc, 0, SEEK_CUR)) - oldSize ); - //seek to beginning of data, which is requested - lseek(desc, -(off_t)(TELETEXT_PAGESIZE*TOC_SIZE), SEEK_CUR); - return true; - } else - return false; -} - -void PackedStorage::getFilename(char *buffer, int bufLength, PageID page) { - //This is a different scheme: page 576_07 will have the name 570s.vtx, the same as e.g. 571_01 or 575_00 - //Think of "the five hundred seventies" - snprintf(buffer, bufLength, "%s/%s/%03xs.vtx", getRootDir(), - *page.channel.ToString(), (page.page & 0xFF0)); -} - -StorageHandle PackedStorage::openForWriting(PageID page) { - static bool wroteError=false; - char filename[PATH_MAX]; - getFilename(filename, sizeof(filename), page); - //first try - int desc=open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (desc != -1) { - if (!seekTo(page, desc, true)) { - ::close(desc); - return StorageHandle(); - } - if ( maxBytes && byteCount>maxBytes ) - freeSpace(); - return (StorageHandle)desc; - } - //no space on disk? make some space available - if (errno == ENOSPC) - freeSpace(); - //second try - desc=open(filename, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); - if (desc==-1 && !wroteError) { - //report error to syslog - once! - wroteError=true; - esyslog("OSD-Teletext: Error opening teletext file %s: %s", filename, strerror(errno)); - } - - if (desc==-1) - return StorageHandle(); - else if (!seekTo(page, desc, true)) { - ::close(desc); - return StorageHandle(); - } - - if ( maxBytes && byteCount>maxBytes ) - freeSpace(); - return (StorageHandle)desc; -} - -StorageHandle PackedStorage::openForReading(PageID page, bool countAsAccess) { - int desc; - if ( (desc=(int)LegacyStorage::openForReading(page, false))!= -1 ) { - if (!seekTo(page, desc, false)) { - //this is not an error condition here, may and shall happen! - ::close(desc); - } else { - return (StorageHandle)desc; - } - } - return StorageHandle(); -} - - - - cTelePage::cTelePage(PageID t_page, uchar t_flags, uchar t_lang,int t_mag, Storage *s) : mag(t_mag), flags(t_flags), lang(t_lang), page(t_page), storage(s) { @@ -8,7 +8,7 @@ * * ***************************************************************************/ -#ifndef __TXTRECV_H +#ifndef __TXTRECV_H #define __TXTRECV_H #include <vdr/status.h> @@ -19,113 +19,7 @@ #include <stdio.h> #include <unistd.h> -struct PageID { - PageID() { page=subPage=0; } - PageID(tChannelID id, int p, int s) { set(id, p, s); } - void set(tChannelID id, int p, int s) - { channel=id; page=p; subPage=s; } - tChannelID channel; - int page; - int subPage; -}; - -struct StorageHandle { -public: - StorageHandle() { handle=-1; } - StorageHandle(const StorageHandle &s) { handle=s.handle; } - StorageHandle(int h) { handle=h; } - StorageHandle &operator=(int h) { handle=h; return *this; } - StorageHandle &operator=(const StorageHandle &s) { handle=s.handle; return *this; } - operator bool() const { return handle!=-1; } - operator int() const { return handle; } -private: - int handle; -}; - -class RootDir { -public: - static void setRootDir(const char *); - static const char *getRootDir(); -protected: - static const char *root; -}; - -class Storage : public RootDir { -public: - virtual ~Storage(); - enum StorageSystem { StorageSystemLegacy, StorageSystemPacked }; - //must be called before the first call to instance() - - static Storage *CreateInstance(StorageSystem system, int storageLimit); - - //must be called before operation starts. Set all options (RootDir, maxStorage) before. - virtual void cleanUp() = 0; - - virtual void getFilename(char *buffer, int bufLength, PageID page); - virtual void prepareDirectory(tChannelID chan); - - virtual StorageHandle openForWriting(PageID page) = 0; - virtual StorageHandle openForReading(PageID page, bool countAsAccess) = 0; - virtual ssize_t write(const void *ptr, size_t size, StorageHandle stream) = 0; - virtual ssize_t read(void *ptr, size_t size, StorageHandle stream) = 0; - virtual void close(StorageHandle stream) = 0; -protected: - Storage(); - int cleanSubDir(const char *dir); - int doCleanUp(); - virtual int actualFileSize(int netFileSize) { return netFileSize; } - void freeSpace(); - bool exists(const char* file); - - long byteCount; - cString currentDir; -private: - bool failedFreeSpace; -}; - -class LegacyStorage : public Storage { -private: - void initMaxStorage(int maxMB=-1); -public: - LegacyStorage(int maxMB); - virtual ~LegacyStorage(); - virtual void cleanUp(); - - virtual StorageHandle openForWriting(PageID page); - virtual StorageHandle openForReading(PageID page, bool countAsAccess); - virtual ssize_t write(const void *ptr, size_t size, StorageHandle stream); - virtual ssize_t read(void *ptr, size_t size, StorageHandle stream) - { return ::read((int)stream, ptr, size); } - virtual void close(StorageHandle stream) - { ::close((int)stream); } -protected: - void registerFile(PageID page); - virtual int actualFileSize(int netFileSize); - //int maxPages; - long maxBytes; - int fsBlockSize; - int pageBytes; -}; - -class PackedStorage : public LegacyStorage { -public: - PackedStorage(int maxMB); - - virtual void getFilename(char *buffer, int bufLength, PageID page); - virtual StorageHandle openForWriting(PageID page); - virtual StorageHandle openForReading(PageID page, bool countAsAccess); -protected: - struct PageAddress { - bool operator==(const PageID &id) const - { return page==id.page && subPage==id.subPage; } - void operator=(const PageID &id) - { page=id.page; subPage=id.subPage; } - int page; - int subPage; - }; - bool seekTo(PageID page, int fd, bool create); - void registerFile(PageID page); -}; +#include "storage.h" class cTelePage { private: |