/*****************************************************************************
* DirectoryAdapter.java
*****************************************************************************
* Copyright © 2012-2013 VLC authors and VideoLAN
* Copyright © 2012-2013 Edward Wang
*
* 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.videolan.vlc.gui;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.regex.Pattern;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.vlc.Util;
import org.videolan.vlc.VLCApplication;
import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import org.tribler.triblersvod.gui.R;
public class DirectoryAdapter extends BaseAdapter {
public final static String TAG = "VLC/DirectoryAdapter";
public static boolean acceptedPath(String f) {
return Pattern.compile(Media.EXTENSIONS_REGEX, Pattern.CASE_INSENSITIVE).matcher(f).matches();
}
/**
* Private helper class to implement the directory tree
*/
public class Node implements Comparable<DirectoryAdapter.Node> {
public DirectoryAdapter.Node parent;
public ArrayList<DirectoryAdapter.Node> children;
/**
* Name of the file/folder (not full path).
*
* null on the root node (root selection folder).
*/
String name;
String visibleName;
public Boolean isFile;
public Node(String _name) {
this(_name, null);
}
public Node(String _name, String _visibleName) {
this.name = _name;
this.visibleName = _visibleName;
this.children = new ArrayList<DirectoryAdapter.Node>();
this.isFile = false;
this.parent = null;
}
public void addChildNode(DirectoryAdapter.Node n) {
n.parent = this;
this.children.add(n);
}
public DirectoryAdapter.Node getChildNode(String directoryName) {
for(DirectoryAdapter.Node n : this.children) {
if(n.name.equals(directoryName))
return n;
}
DirectoryAdapter.Node n = new DirectoryAdapter.Node(directoryName);
this.addChildNode(n);
return n;
}
public Boolean isFile() {
return this.isFile;
}
public void setIsFile() {
this.isFile = true;
}
public Boolean existsChild(String _n) {
for(DirectoryAdapter.Node n : this.children) {
if(Util.nullEquals(n.name, _n)) return true;
}
return false;
}
public DirectoryAdapter.Node ensureExists(String _n) {
for(DirectoryAdapter.Node n : this.children) {
if(Util.nullEquals(n.name, _n)) return n;
}
DirectoryAdapter.Node nn = new Node(_n);
this.children.add(nn);
return nn;
}
public int subfolderCount() {
int c = 0;
for(DirectoryAdapter.Node n : this.children) {
if(n.isFile() == false && !n.name.equals("..")) c++;
}
return c;
}
public int subfilesCount() {
int c = 0;
for(DirectoryAdapter.Node n : this.children) {
if(n.isFile() == true) c++;
}
return c;
}
public String getVisibleName() {
return (this.visibleName != null) ? this.visibleName : this.name;
}
@Override
public int compareTo(Node arg0) {
if(this.isFile && !(arg0.isFile))
return 1;
else if(!(this.isFile) && arg0.isFile)
return -1;
else
return String.CASE_INSENSITIVE_ORDER.compare(this.name, arg0.name);
}
}
static class DirectoryViewHolder {
View layout;
TextView title;
TextView text;
ImageView icon;
}
private void populateNode(DirectoryAdapter.Node n, String path) {
populateNode(n, path, 0);
}
/**
* @param n Node to populate
* @param path Path to populate
* @param depth Depth of iteration (0 = 1st level of nesting, 1 = 2 level of nesting, etc)
*/
private void populateNode(DirectoryAdapter.Node n, String path, int depth) {
if (path == null) {
// We're on the storage list
String storages[] = Util.getMediaDirectories();
for (String storage : storages) {
File f = new File(storage);
DirectoryAdapter.Node child = new DirectoryAdapter.Node(f.getName(), getVisibleName(f));
child.isFile = false;
this.populateNode(child, storage, 0);
n.addChildNode(child);
}
return;
}
File file = new File(path);
if(!file.exists() || !file.isDirectory())
return;
ArrayList<String> files = new ArrayList<String>();
LibVLC.nativeReadDirectory(path, files);
StringBuilder sb = new StringBuilder(100);
/* If no sub-directories or I/O error don't crash */
if(files == null || files.size() < 1) {
//return
} else {
for(int i = 0; i < files.size(); i++) {
String filename = files.get(i);
/* Avoid infinite loop */
if(filename.equals(".") || filename.equals("..") || filename.startsWith(".")) continue;
DirectoryAdapter.Node nss = new DirectoryAdapter.Node(filename);
nss.isFile = false;
sb.append(path);
sb.append("/");
sb.append(filename);
String newPath = sb.toString();
sb.setLength(0);
// Don't try to go beyond depth 10 as a safety measure.
if (LibVLC.nativeIsPathDirectory(newPath) && depth < 10) {
ArrayList<String> files_int = new ArrayList<String>();
LibVLC.nativeReadDirectory(newPath, files_int);
if(files_int.size() < 8) { /* Optimisation: If there are more than 8
sub-folders, don't scan each one, otherwise
when scaled it is very slow to load */
String mCurrentDir_old = mCurrentDir;
mCurrentDir = path;
this.populateNode(nss, newPath, depth+1);
mCurrentDir = mCurrentDir_old;
}
} else {
if(acceptedPath(newPath))
nss.setIsFile();
else
continue;
}
n.addChildNode(nss);
}
Collections.sort(n.children);
}
DirectoryAdapter.Node up = new DirectoryAdapter.Node("..");
n.children.add(0, up);
}
private LayoutInflater mInflater;
private DirectoryAdapter.Node mRootNode;
private DirectoryAdapter.Node mCurrentNode;
private String mCurrentDir;
private String mCurrentRoot;
public DirectoryAdapter() {
DirectoryAdapter_Core(null);
}
private void DirectoryAdapter_Core(String rootDir) {
if (rootDir != null)
rootDir = Util.stripTrailingSlash(rootDir);
Log.v(TAG, "rootMRL is " + rootDir);
mInflater = LayoutInflater.from(VLCApplication.getAppContext());
mRootNode = new DirectoryAdapter.Node(rootDir);
mCurrentDir = rootDir;
this.populateNode(mRootNode, rootDir);
mCurrentNode = mRootNode;
}
@Override
public boolean hasStableIds() {
// TODO Auto-generated method stub
return false;
}
@Override
public int getCount() {
return mCurrentNode.children.size();
}
@Override
public Object getItem(int arg0) {
// TODO Auto-generated method stub
return null;
}
@Override
public long getItemId(int arg0) {
// TODO Auto-generated method stub
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
DirectoryAdapter.Node selectedNode = mCurrentNode.children.get(position);
DirectoryViewHolder holder;
View v = convertView;
Context context = VLCApplication.getAppContext();
/* If view not created */
if (v == null) {
v = mInflater.inflate(R.layout.directory_view_item, parent, false);
holder = new DirectoryViewHolder();
holder.layout = v.findViewById(R.id.layout_item);
holder.title = (TextView) v.findViewById(R.id.title);
holder.text = (TextView) v.findViewById(R.id.text);
holder.icon = (ImageView) v.findViewById(R.id.dvi_icon);
v.setTag(holder);
} else
holder = (DirectoryViewHolder) v.getTag();
String holderText = "";
if(selectedNode.isFile()) {
Log.d(TAG, "Loading media " + selectedNode.name);
Media m = new Media(LibVLC.getExistingInstance(), getMediaLocation(position));
holder.title.setText(m.getTitle());
holderText = m.getSubtitle();
} else
holder.title.setText(selectedNode.getVisibleName());
if(selectedNode.name.equals(".."))
holderText = context.getString(R.string.parent_folder);
else if(!selectedNode.isFile()) {
int folderCount = selectedNode.subfolderCount();
int mediaFileCount = selectedNode.subfilesCount();
holderText = "";
if(folderCount > 0)
holderText += context.getResources().getQuantityString(
R.plurals.subfolders_quantity, folderCount, folderCount
);
if(folderCount > 0 && mediaFileCount > 0)
holderText += ", ";
if(mediaFileCount > 0)
holderText += context.getResources().getQuantityString(
R.plurals.mediafiles_quantity, mediaFileCount,
mediaFileCount);
}
holder.text.setText(holderText);
if(selectedNode.isFile())
holder.icon.setImageResource(R.drawable.icon);
else
holder.icon.setImageResource(R.drawable.ic_folder);
return v;
}
public boolean browse(int position) {
DirectoryAdapter.Node selectedNode = mCurrentNode.children.get(position);
if(selectedNode.isFile()) return false;
return browse(selectedNode.name);
}
public boolean browse(String directoryName) {
if (this.mCurrentDir == null) {
// We're on the storage list
String storages[] = Util.getMediaDirectories();
for (String storage : storages) {
storage = Util.stripTrailingSlash(storage);
if (storage.endsWith(directoryName)) {
this.mCurrentRoot = storage;
this.mCurrentDir = Util.stripTrailingSlash(storage);
break;
}
}
} else {
try {
this.mCurrentDir = new URI(
LibVLC.PathToURI(this.mCurrentDir + "/" + directoryName))
.normalize().getPath();
this.mCurrentDir = Util.stripTrailingSlash(this.mCurrentDir);
if (this.mCurrentDir.equals(getParentDir(this.mCurrentRoot))) {
// Returning to the storage list
this.mCurrentDir = null;
this.mCurrentRoot = null;
}
} catch(URISyntaxException e) {
Log.e(TAG, "URISyntaxException in browse()", e);
return false;
} catch(NullPointerException e) {
Log.e(TAG, "NullPointerException in browse()", e);
return false;
}
}
Log.d(TAG, "Browsing to " + this.mCurrentDir);
if(directoryName.equals(".."))
this.mCurrentNode = this.mCurrentNode.parent;
else {
this.mCurrentNode = this.mCurrentNode.getChildNode(directoryName);
if(mCurrentNode.subfolderCount() < 1) {
// Clear the ".." entry
this.mCurrentNode.children.clear();
this.populateNode(mCurrentNode, mCurrentDir);
}
}
this.notifyDataSetChanged();
return true;
}
public boolean isChildFile(int position) {
DirectoryAdapter.Node selectedNode = mCurrentNode.children.get(position);
return selectedNode.isFile();
}
public String getMediaLocation(int position) {
if (position >= mCurrentNode.children.size())
return null;
return LibVLC.PathToURI(
this.mCurrentDir + "/" + mCurrentNode.children.get(position).name
);
}
public boolean isRoot() {
return mCurrentDir == null;
}
public String getmCurrentDir() {
return mCurrentDir;
}
public ArrayList<String> getAllMediaLocations() {
ArrayList<String> a = new ArrayList<String>();
// i = 1 to exclude ".." folder
for(int i = 1; i < mCurrentNode.children.size(); i++)
if(mCurrentNode.children.get(i).isFile)
a.add(getMediaLocation(i));
return a;
}
public void refresh() {
for(DirectoryAdapter.Node n : this.mCurrentNode.children)
n.children.clear();
this.mCurrentNode.children.clear();
this.populateNode(mCurrentNode, mCurrentDir);
this.notifyDataSetChanged();
}
private String getParentDir(String path) {
try {
path = new URI(LibVLC.PathToURI(path + "/.."))
.normalize().getPath();
} catch (URISyntaxException e) {
e.printStackTrace();
}
return Util.stripTrailingSlash(path);
}
private String getVisibleName(File file) {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
// Show "sdcard" for the user's folder when running in multi-user
if (file.getAbsolutePath().equals(Environment.getExternalStorageDirectory().getPath())) {
return VLCApplication.getAppContext().getString(R.string.internal_memory);
}
}
return file.getName();
}
}