Parser.java 12.8 KB
package com.upc.pbe.upcnews;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;

import android.app.Activity;
import android.widget.Toast;

//Parser d'arxius m3u8
public class Parser{
	
	private static final String STARTWORD = "#EXTM3U";
	private String path; //CWD, completes implicit paths such as "file.m3u8" appending the CWD in order to download the file
	private int fileType; //Indica si es segment list o media list
	private int currentLine;
	private int currentSegment; //Cada segment de cada qualitat distinta
	private int currentList; //Index de cada recurso distint
	private boolean expectSegment = false;
	private boolean expectList = false;
	private Descarrega download;
	private Activity caller; //On enviar les notificacions i warnings
	private boolean validated; //Indica si la llista comen�a amb #EXTM3U o no
	private String server;

	public Parser(String p, Activity caller){
		this.caller = caller;
		path = p;
		String host = p.substring(7);
		server = host.substring(0,host.indexOf("/"));
		currentLine = 0;
		currentSegment = 0;
		currentList = 0;
		fileType = -1;//-1 indica UNDETERMINED, 0 indica SEGMENTS, 1 indica MEDIA
		download = new Descarrega();
		validated = false;
	}

	public ArrayList<ParentList> parseFile(String file) throws ErrorException, WarningException, InfoException {
		//Parseja un arxiu m3u8
		ArrayList<ParentList> lists = new ArrayList<ParentList>();
		lists.add(new ParentList(""));
		lists.get(0).getLists().add(new Video(-1));
		String[] lines = file.split("\n");
		for (int i = 0; i < lines.length; i++){
			if (lines[i].endsWith("\\")){
				//Barreja les entrades multilinea, que son les que acaben en backslash
				lines[++i] = lines[i - 1].substring(0, lines[i - 1].indexOf("\\") - 1)
						+ lines[i];
			}
			try{
				parseLine(lines[i], lists);
			}
			catch (InfoException iE){
				Toast.makeText(caller, iE.getMessage(), Toast.LENGTH_SHORT).show();
			}
			catch (WarningException wE){
				Toast.makeText(caller, wE.getMessage(), Toast.LENGTH_SHORT).show();
			}
		}
		/*
		 * Esto contiene una lista de RECURSOS, 
		 * que a su vez contiene una lista de CALIDADES. 
		 * Si no te gusta mis VERSOS,
		 * te mando a la mierda sin SUTILIDADES.
		 * 
		 * -- Imanol, hasta las cejas de cafeina.
		 * 
		 * PD: En el ultimo nivel de profundidad hay una lista de segmentos
		 * (debajo de calidades). Alli esta el autentico tesoro de Narnia.
		 */
		for (int i = 0; i < lists.size(); i++){
			sortQuality(lists.get(i));
		}
		return lists;
	}

	private int searchID(String ID, ArrayList<ParentList> lists){
		for (int i = 0; i < lists.size(); i++)
		{
			if (lists.get(i).getID().equals(ID))
			{
				return i;
			}
		}
		return -1;
	}

	private void sortQuality(ParentList ppls){
		ArrayList<Video> lists = ppls.getLists();
		if (lists.get(0).getQuality() == -1){
			//Es el cas de Media Lists i Segment Lists, no tenen diferenciacio de qualitats
			return;
		}
		//Bubblesort
		while (true){
			boolean sorted = true;
			int i = 0;
			do{
				if (lists.get(i).getQuality() < lists.get(i + 1).getQuality()){
					sorted = false;
					Video aux = lists.get(i);
					lists.set(i, lists.get(i + 1));
					lists.set(i + 1, aux);
				}

			}while (++i != lists.size() - 1);
			if (sorted){
				break;
			}
		}

	}

	public void parseLine(String line, ArrayList<ParentList> lists) throws ErrorException, WarningException, InfoException {
		//Parseja una linea de l'arxiu
		if (line.isEmpty()){
			currentLine++;
			return;
		}
		else{
			ParentList ppls = lists.get(currentList);
			Video pls = ppls.getLists().get(ppls.getLists().size() - 1);
			if (!validated){
				if (line.equals(STARTWORD)){
					validated = true;
				}
				else{
					throw new ErrorException("Playlist no valida");
				}
			}
			else{
				if (line.charAt(0) == '#'){
					if (line.substring(0, 4).equals("#EXT")){
						if (expectSegment && !line.startsWith("#EXT-X-BYTERANGE")){
							throw new ErrorException("S'esperava segment URI, en comptes tenim EXT-TAG");
						}
						if (expectList){
							throw new ErrorException("S'esperava llista, en comptes tenim EXT-TAG");
						}
						String[] extTag = line.split(":", 2);
						if (extTag[0].equals("#EXT-X-MEDIA-SEQUENCE")){
							pls.setSequence(Integer.parseInt(extTag[1]));
						}
						else if (extTag[0].equals("#EXT-X-TARGETDURATION")){
							pls.setMaxDuration(Integer.parseInt(extTag[1]));
						}
						else if (extTag[0].equals("#EXTINF")){
							if (fileType == -1){
								fileType = 0;
							}
							else if (fileType == 1){
								throw new ErrorException("Arxiu invalid, hauria de contenir llistes pero s'han trobat segments");
							}
							String[] args = extTag[1].split(",");
							float duration = Float.parseFloat(args[0]);
							if (duration > pls.getMaxDuration()){
								throw new ErrorException("Segment " + currentSegment + 1 + " en linea " + currentLine + " supera la duracio maxima");
							}
							Segment s = new Segment(duration);
							if (args.length == 1){
								s.setName("");
							}
							else{
								s.setName(args[1]);
							}
							pls.getSegments().add(s);
							expectSegment = true;
						}
						else if (extTag[0].equals("#EXT-X-STREAM-INF")){
							if (fileType == -1){
								fileType = 1;
							}
							else if (fileType == 0){
								throw new ErrorException("Arxiu invalid, hauria de contenir segments pero s'han trobat llistes");
							}
							expectList = true;
							String programID = "";
							double bandwidth = -1;
							String[] arguments = extTag[1].split(",");
							for (int i = 0; i < arguments.length; i++){
								String[] argument = arguments[i].split("=");
								//Si l'argument en questio conte diferents valors, detecta el caracter " i els ajunta en un String
								if (argument[1].startsWith("\"") && !argument[1].endsWith("\"")){
									int j = i;
									while (true){
										if (i == arguments.length - 1){
											throw new ErrorException("Argument mal expressat en linea " + currentLine);
										}
										if (arguments[++i].endsWith("\"")){
											String end = arguments[i];
											for (int k = j + 1; k < i; k++){
												argument[1] += arguments[k];
											}
											argument[1] += end;
											break;
										}
									}
								}
								if (argument[0].equals("PROGRAM-ID")){
									programID = argument[1];
								}
								else if (argument[0].equals("BANDWIDTH")){
									bandwidth = Double.parseDouble(argument[1]);
								}
							}
							if (programID.equals("") || bandwidth == -1){
								throw new ErrorException("Playlist en linea " + currentLine + " no te arguments");
							}
							if (!programID.equals(ppls.getID())){
								//Si el Program-ID no coincideix, buscar o crear un de nou
								if (ppls.getID().equals("")){
									//Si es treballa amb la ParentList per defecte, modificar, no crear una nova
									lists.get(0).setID(programID);
								}
								else{
									currentList = searchID(programID, lists);
									if (currentList == -1){
										//No s'ha trobat ParentList, es crea una nova
										lists.add(new ParentList(programID));
										currentList = lists.size() - 1;
									}
									ppls = lists.get(currentList);
									pls = ppls.getLists().get(ppls.getLists().size() - 1);
								}
							}
							//Creem una List dins el ParentList correcte
							if ((ppls.getLists().size() == 1) && (pls.getQuality() == -1) && pls.getSegments().isEmpty()){
								//Si es treballa amb la List per defecte, modificar, no crear una nova
								pls.setQuality(bandwidth);
							}
							else{
								ppls.getLists().add(new Video(bandwidth));
							}
						}
						else if (extTag[0].equals("#EXT-X-MEDIA")){
							if (fileType == -1){
								fileType = 1;
							}
							else if (fileType == 0){
								throw new ErrorException("Arxiu invalid, hauria de contenir segments pero s'han trobat llistes");
							}
							String Type = "";
							String Name = "";
							String GroupID = "";
							String URI = "";
							boolean Default = false;
							String[] arguments = extTag[1].split(",");
							for (int i = 0; i < arguments.length; i++){
								String[] argument = arguments[i].split("=");
								//Si l'argument en questio conte diferents valors, detecta el caracter " i els ajunta en un String
								if (argument[1].startsWith("\"") && !argument[1].endsWith("\"")){
									int j = i;
									while (true){
										if (i == arguments.length - 1){
											throw new ErrorException("Argument mal expressat en linea " + currentLine);
										}
										if (arguments[++i].endsWith("\"")){
											String end = arguments[i];
											for (int k = j + 1; k < i; k++){
												argument[1] += arguments[k];
											}
											argument[1] += end;
											break;
										}
									}
								}
								if (argument[0].equals("NAME")){
									Name = argument[1].substring(1, argument[1].length() - 1);
								}
								else if (argument[0].equals("TYPE")){
									Type = argument[1];
								}
								else if (argument[0].equals("GROUP-ID")){
									GroupID = argument[1].substring(1, argument[1].length() - 1);
								}
								else if (argument[0].equals("DEFAULT")){
									if (argument[1].equals("YES")){
										Default = true;
									}
									else if (argument[1].equals("NO")){
										Default = true;
									}
									else{
										throw new ErrorException("Valor invalid per l'argument DEFAULT en linea " + currentLine);
									}
								}
								else if (argument[0].equals("URI")){
									if (!argument[1].contains("http://")){
										//En cas de que la ruta llegida es RELATIVE
										URI = path + argument[1].substring(1, argument[1].length() - 1);
									}
									else{
										URI = argument[1].substring(1, argument[1].length() - 1);
									}
								}
							}
							if (Type.equals("") || Name.equals("") || URI.equals("") || GroupID.equals("")) {
								throw new ErrorException("Playlist en linea " + currentLine + " no te arguments");
							}
							Parser p = new Parser(URI.substring(0, URI.lastIndexOf("/") + 1), caller);
							Video newList;
							try{
								newList = p.parseFile(download.doInBackground(URI)).get(0).getLists().get(0);
								if (!GroupID.equals(ppls.getID())){
									if (ppls.getID().equals("")){
										//Si es treballa amb la ParentList per defecte, modificar, no crear una nova
										ppls.setDefault(Default);
										ppls.setName(Name);
										ppls.setType(Type);
										ppls.getLists().set(0, newList);
										ppls.setID(GroupID);
									}
									else{
										currentList = searchID(GroupID, lists);
										if (currentList == -1){
											ParentList pl = new ParentList(GroupID);
											pl.setDefault(Default);
											pl.setName(Name);
											pl.setType(Type);
											lists.add(pl);
											currentList = lists.size() - 1;
										}
										ppls = lists.get(currentList);
										ppls.getLists().add(newList);
									}
								}
								else{
									ppls.getLists().add(newList);
								}
							}
							catch (IOException e){
								// Nunca llegara aqui la cosa, y si lo hace ES CULPA DEL SERVIDOR!!
							}
						}
						else if (extTag[0].equals("#EXT-X-ENDLIST")){
							if (expectSegment || expectList){
								throw new ErrorException("Final de llista no esperat!");
							}
						}
						else if (extTag[0].equals("#EXT-X-VERSION")){
							//IGNORE
						}
						else if (extTag[0].equals("#EXT-X-PLAYLIST-TYPE")){
							//IGNORE
						}
						else{
							currentLine++;
							throw new WarningException("Tag no implementat: " + extTag[0]);
						}
					}
					else{
						String comment = line.substring(1);
						throw new InfoException("Comentari en linea " + currentLine++ + " " + comment);
					}
				}
				else{
					if (expectSegment){
						expectSegment = false;
						try{
							if(!line.startsWith("http://") && !line.startsWith("/"))
							{
								line = path + line;
							}
							else if(line.startsWith("/"))
							{
								line = server + line;
							}
							pls.getSegments().get(currentSegment).setURL(line);
						}
						catch (MalformedURLException e){
							throw new ErrorException(e.getMessage());
						}
						currentSegment++;
					}
					else if (expectList){
						//Descarregar llista i juntar les entrades amb les de la llista actual
						expectList = false;
						Parser p = new Parser(line.substring(0, line.lastIndexOf("/") + 1), caller);
						Video newList;
						try{
							newList = p.parseFile(download.doInBackground(line)).get(0).getLists().get(0);
							pls.setMaxDuration(newList.getMaxDuration());
							pls.setSequence(newList.getSequence());
							for (int i = 0; i < newList.getSegments().size(); i++){
								pls.getSegments().add(newList.getSegments().get(i));
							}
						}
						catch (IOException e){
							// Nunca llegara aqui la cosa, y si lo hace ES CULPA DEL SERVIDOR!!
						}
					}
					else{
						throw new ErrorException("ERROR: String no esperat" + line);
					}
				}
			}
			currentLine++;
		}
	}
}