package de.sosd.mediaserver.service;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import de.sosd.mediaserver.dao.DidlDao;
import de.sosd.mediaserver.dao.FilesystemDao;
import de.sosd.mediaserver.dao.SystemDao;
import de.sosd.mediaserver.domain.db.DidlDomain;
import de.sosd.mediaserver.domain.db.FileDomain;
import de.sosd.mediaserver.domain.db.ScanFolderDomain;
import de.sosd.mediaserver.domain.db.ScanFolderState;
import de.sosd.mediaserver.domain.db.SystemDomain;
import de.sosd.mediaserver.service.dlna.DIDLService;
import de.sosd.mediaserver.util.DidlChangeMap;
import de.sosd.mediaserver.util.ScanContext;
import de.sosd.mediaserver.util.ScanFile;
import de.sosd.mediaserver.util.ScanFolder;
@Service
public class FilesystemService {
private final static Log logger = LogFactory.getLog(FilesystemService.class);
@Autowired
private FilesystemDao fsDao;
@Autowired
private SystemDao systemDao;
@Autowired
private DidlDao didlDao;
@Autowired
private IdService idservice;
@Autowired
private DIDLService didl;
@Autowired
private MediaserverConfiguration cfg;
public boolean addScanDirectory(final File directory) {
try {
addScanDirectoryTransactional(directory);
return true;
} catch (final Throwable t) {
logger.error("error while adding directory, probably because it already exists", t);
return false;
}
}
@Transactional(propagation=Propagation.REQUIRED)
public void addScanDirectoryTransactional(final File directory) {
if (directory.isDirectory()) {
final String id = this.idservice.getId(directory);
if (!this.fsDao.isDirectoryPresent(id)) {
final ScanFolderDomain scanFolder = new ScanFolderDomain(id, directory, null);
final SystemDomain system = this.systemDao.getSystem(cfg.getUSN());
system.getScanFolder().add(scanFolder);
scanFolder.setSystem(system);
scanFolder.setDidlRoot(this.didl.createDidlContainer(scanFolder, system.getDidlRoot()));
this.systemDao.store(system);
logger.info("added new scan-directory : " + scanFolder.getPath());
}
}
}
public void scanFilesystem() {
// get list of directories due for scanning
logger.info("scanner [start]");
final List<ScanContext> scanContexts = createScanContexts();
scanDirectories(scanContexts);
}
@Transactional(propagation=Propagation.REQUIRED)
private List<ScanContext> createScanContexts() {
final List<ScanContext> scanContexts = new ArrayList<ScanContext>();
final long currentTimeMillis = System.currentTimeMillis();
final SystemDomain system = this.systemDao.getSystem(cfg.getUSN());
for (final ScanFolderDomain sfd : system.getScanFolder()) {
if (
!ScanFolderState.SCANNING.equals(sfd.getScanState()) &&
((sfd.getLastScan() == null) ||
((sfd.getLastScan().getTime() + (sfd.getScanInterval() * 60000)) < currentTimeMillis))) {
// should scan
final File dir = new File(sfd.getPath());
if (! dir.exists() || ! dir.isDirectory()) {
// this could happen with external storage ..
sfd.setScanState(ScanFolderState.NOT_FOUND);
didlDao.setOnline(sfd.getId(), false);
system.increaseUpdateId();
} else {
if (ScanFolderState.NOT_FOUND.equals(sfd.getScanState())) {
didlDao.setOnline(sfd.getId(), true);
system.increaseUpdateId();
}
sfd.setScanState(ScanFolderState.SCANNING);
scanContexts.add(new ScanContext(sfd.getId(), dir));
}
}
}
this.systemDao.store(system);
return scanContexts;
}
@Transactional(propagation=Propagation.REQUIRED)
private void scanDirectories(final List<ScanContext> scanContexts) {
long changedCount = 0l;
boolean systemChanged = false;
final SystemDomain system = this.systemDao.getSystem(cfg.getUSN());
final Set<Object> itemsToPurge = new HashSet<Object>();
final Map<String, ScanContext> idScanContextMap = new HashMap<String, ScanContext>();
final Set<String> foundFileIds = new HashSet<String>();
final DidlChangeMap touchedDidlMap = new DidlChangeMap();
for (final ScanContext sc : scanContexts) {
idScanContextMap.put(sc.getScanFolderId(), sc);
}
// collect all Files, no Folders!
for (final ScanContext sc : scanContexts) {
logger.info("scanner [scan] "+sc.getScanFolder());
List<String> knownFileIds = this.fsDao.getAllFileIds(sc.getScanFolderId());
collectNewFiles(sc.getScanFolder(), sc.getFiles(), knownFileIds, foundFileIds);
logger.info("scanner [found files] " + sc.getFiles().size() + " " +sc.getScanFolder());
changedCount += sc.getFiles().size();
knownFileIds.removeAll(foundFileIds);
for (String removed : knownFileIds) {
sc.addDeletedMediaFile(removed);
changedCount += 1;
}
}
if (changedCount > 0) {
final List<String> allDidlIds= this.didlDao.getAllDidlIds();
for (String id : allDidlIds) {
touchedDidlMap.addDidl(id, null);
}
for (final ScanFolderDomain sfd : system.getScanFolder()) {
if (idScanContextMap.containsKey(sfd.getId())) {
final ScanContext sc = idScanContextMap.get(sfd.getId());
logger.info("scanner [filter] "+sc.getScanFolder());
for (final ScanFile f : sc.getFiles()) {
final FileDomain fd = new FileDomain(f.getId(), null, f.getFile());
if (this.didl.createDidl(fd, f, touchedDidlMap, sfd)) {
sc.getMediaFiles().add(fd);
}
}
logger.info("scanner [found new files] " + sc.getMediaFiles().size() + " " +sc.getScanFolder());
}
}
}
// update folders
for (final ScanFolderDomain sfd : system.getScanFolder()) {
if (idScanContextMap.containsKey(sfd.getId())) {
final ScanContext sc = idScanContextMap.get(sfd.getId());
boolean changedFiles = false;
for (final FileDomain fd : sc.getMediaFiles()) {
changedFiles |= sfd.addFile(fd);
logger.info("scanner [add file] " + fd.getName() + "\t\t(" + fd.getPath() + ")");
}
if (changedFiles || !sc.getDeletedMediaFiles().isEmpty()) {
changedFiles |= updateDidl(sfd.getDidlRoot(), sfd, touchedDidlMap,sc.getDeletedMediaFiles(), itemsToPurge);
}
// remove scanfolder mark
sfd.setScanState(ScanFolderState.IDLE);
sfd.setLastScan(new Date());
systemChanged |= changedFiles;
}
}
// something changed increase updateId
if (systemChanged) {
logger.info("scanner [collect new stats]");
system.increaseUpdateId();
system.setLastDataChange(new Date());
if (this.didl.foundUnsupportedFiles()) {
logger.info("scanner [dlna-unsupported] " + this.didl.getMissingClassTypeExtensions()+ ", " + this.didl.getMissingProtocolInfoExtensions());
}
}
logger.info("scanner [update database]");
this.systemDao.update(system, new ArrayList<Object>(itemsToPurge));
logger.info("scanner [done]");
}
@Transactional(propagation=Propagation.REQUIRED)
private boolean updateDidl(final DidlDomain root, final ScanFolderDomain sfd, DidlChangeMap map, final Set<String> removedItemIds, final Set<Object> itemsToPurge) {
boolean changed = false;
// remove files first
for (String removedId : removedItemIds) {
if (map.hasDidl(removedId)) {
DidlDomain item = map.getDidl(removedId);
changed |= removeDidl(root, map,item, itemsToPurge);
}
}
return changed;
}
@Transactional(propagation=Propagation.REQUIRED)
private boolean removeDidl(DidlDomain root, DidlChangeMap map,
DidlDomain item, Set<Object> itemsToPurge) {
if (root.getId().equals(item.getId())) {
return false;
}
boolean changed = false;
DidlDomain parent;
if (item.getParent() != null) {
parent = map.getDidl(item.getParent().getId());
// remove child
final FileDomain file = item.getFile();
changed = parent.removeChild(item);
didlDao.store(parent);
// item.setFile(null);
// item.setParent(null);
// item.setReference(null);
// for (DidlDomain ref : item.getReferences()) {
// removeDidl(root, map, ref, itemsToPurge);
// }
// for (DidlDomain content : item.getContainerContent()) {
// removeDidl(root, map, content, itemsToPurge);
// }
// item.getContainerContent().clear();
if (file != null) {
changed |= file.getParent().removeFile(file);
// file.setParent(null);
// file.setDidl(null);
// storage.removeFile(file);
logger.info("scanner [removed file] " + file.getName() + "\t(" + file.getPath() + ")");
} else {
logger.info("scanner [removed folder] " + item.getTitle() + "\t(" + item.getId() + ")");
}
if (parent.getContainerContent().isEmpty()) {
changed |= removeDidl(root, map, parent, itemsToPurge);
}
}
return changed;
}
private void collectNewFiles(final ScanFolder folder, final List<ScanFile> list, final List<String> knownFileIds, Set<String> foundFileIds) {
if ((folder.getFile().listFiles() != null) && folder.getFile().isDirectory() && folder.getFile().canRead()) {
for (final File f : folder.getFile().listFiles()) {
final String id = this.idservice.getId(f);
if (f.isFile()) {
foundFileIds.add(id);
if (! knownFileIds.contains(id)) {
ScanFile addedFile = folder.addFile(id, f);
if (addedFile != null) {
list.add(addedFile);
}
}
} else {
ScanFolder addedFolder = folder.addFolder(id, f);
if (addedFolder != null) {
collectNewFiles(addedFolder,list,knownFileIds,foundFileIds);
}
}
}
}
}
}