New MP4 muxer + Queue changes + Storage fixes

Main changes:
* correctly check the available space (CircularFile.java)
* misc cleanup (CircularFile.java)
* use the "Error Reporter" for non-http errors
* rewrite network state checking and add better support for API 21 (Lollipop) or higher
* implement "metered networks"
* add buttons in "Downloads" activity to start/pause all pending downloads, ignoring the queue flag or if the network is "metered"
* add workaround for VPN connections and/or network switching. Example: switching WiFi to 3G
* rewrite DataReader ¡Webm muxer is now 57% more faster!
* rewrite CircularFile, use file buffers instead of memory buffers. Less troubles in low-end devices
* fix missing offset for KaxCluster (WebMWriter.java), manifested as no thumbnails on file explorers

Download queue:
* remember queue status, unless the user pause the download (un-queue)
* semi-automatic downloads, between networks. Effective if the user create a new download or the downloads activity is starts
* allow enqueue failed downloads
* new option, queue limit, enabled by default. Used to allow one or multiple downloads at same time

Miscellaneous:
* fix crash while selecting details/error menu (mistake on MissionFragment.java)
* misc serialize changes (DownloadMission.java)
* minor UI tweaks
* allow overwrite paused downloads
* fix wrong icons for grid/list button in downloads
* add share option
* implement #2006
* correct misspelled word in strings.xml (es) (cmn)
* fix MissionAdapter crash during device shutdown

New Mp4Muxer + required changes:
* new mp4 muxer (from dash only) with this, muxing on Android 7 is possible now!!!
* re-work in SharpStream
* drop mp4 dash muxer
* misc changes: add warning in SecondaryStreamHelper.java,
* strip m4a DASH files to normal m4a format (youtube only)

Fix storage issues:
* warn to the user if is choosing a "read only" download directory (for external SD Cards), useless is rooted :)
* "write proof" allow post-processing resuming only if the device ran out of space
* implement "insufficient storage" error for downloads
This commit is contained in:
kapodamy 2019-03-22 22:54:07 -03:00
parent 1684a2110c
commit 9e34fee58c
49 changed files with 2715 additions and 1936 deletions

View file

@ -21,6 +21,8 @@ import us.shandian.giga.get.DownloadMission;
import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission;
import us.shandian.giga.get.sqlite.DownloadDataSource;
import us.shandian.giga.service.DownloadManagerService.DMChecker;
import us.shandian.giga.service.DownloadManagerService.MissionCheck;
import us.shandian.giga.util.Utility;
import static org.schabi.newpipe.BuildConfig.DEBUG;
@ -28,7 +30,7 @@ import static org.schabi.newpipe.BuildConfig.DEBUG;
public class DownloadManager {
private static final String TAG = DownloadManager.class.getSimpleName();
enum NetworkState {Unavailable, WifiOperating, MobileOperating, OtherOperating}
enum NetworkState {Unavailable, Operating, MeteredOperating}
public final static int SPECIAL_NOTHING = 0;
public final static int SPECIAL_PENDING = 1;
@ -45,7 +47,9 @@ public class DownloadManager {
private NetworkState mLastNetworkStatus = NetworkState.Unavailable;
int mPrefMaxRetry;
boolean mPrefCrossNetwork;
boolean mPrefMeteredDownloads;
boolean mPrefQueueLimit;
private boolean mSelfMissionsControl;
/**
* Create a new instance
@ -152,8 +156,8 @@ public class DownloadManager {
}
mis.postprocessingState = 0;
mis.errCode = DownloadMission.ERROR_POSTPROCESSING;
mis.errObject = new RuntimeException("stopped unexpectedly");
mis.errCode = DownloadMission.ERROR_POSTPROCESSING_STOPPED;
mis.errObject = null;
} else if (exists && !dl.isFile()) {
// probably a folder, this should never happens
if (!sub.delete()) {
@ -162,20 +166,21 @@ public class DownloadManager {
continue;
}
if (!exists) {
if (!exists && mis.isInitialized()) {
// downloaded file deleted, reset mission state
DownloadMission m = new DownloadMission(mis.urls, mis.name, mis.location, mis.kind, mis.postprocessingName, mis.postprocessingArgs);
m.timestamp = mis.timestamp;
m.threadCount = mis.threadCount;
m.source = mis.source;
m.maxRetry = mis.maxRetry;
m.nearLength = mis.nearLength;
m.setEnqueued(mis.enqueued);
mis = m;
}
mis.running = false;
mis.recovered = exists;
mis.metadata = sub;
mis.maxRetry = mPrefMaxRetry;
mis.mHandler = mHandler;
mMissionsPending.add(mis);
@ -205,17 +210,25 @@ public class DownloadManager {
synchronized (this) {
// check for existing pending download
DownloadMission pendingMission = getPendingMission(location, name);
if (pendingMission != null) {
// generate unique filename (?)
try {
name = generateUniqueName(location, name);
} catch (Exception e) {
Log.e(TAG, "Unable to generate unique name", e);
name = System.currentTimeMillis() + name;
Log.i(TAG, "Using " + name);
if (pendingMission.running) {
// generate unique filename (?)
try {
name = generateUniqueName(location, name);
} catch (Exception e) {
Log.e(TAG, "Unable to generate unique name", e);
name = System.currentTimeMillis() + name;
Log.i(TAG, "Using " + name);
}
} else {
// dispose the mission
mMissionsPending.remove(pendingMission);
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_DELETED);
pendingMission.delete();
}
} else {
// check for existing finished download
// check for existing finished download and dispose (if exists)
int index = getFinishedMissionIndex(location, name);
if (index >= 0) mDownloadDataSource.deleteMission(mMissionsFinished.remove(index));
}
@ -242,14 +255,17 @@ public class DownloadManager {
mission.timestamp = System.currentTimeMillis();
}
mSelfMissionsControl = true;
mMissionsPending.add(mission);
// Before starting, save the state in case the internet connection is not available
// Before continue, save the metadata in case the internet connection is not available
Utility.writeToFile(mission.metadata, mission);
if (canDownloadInCurrentNetwork() && (getRunningMissionsCount() < 1)) {
boolean start = !mPrefQueueLimit || getRunningMissionsCount() < 1;
if (canDownloadInCurrentNetwork() && start) {
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS);
mission.start();
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_RUNNING);
}
}
}
@ -257,13 +273,14 @@ public class DownloadManager {
public void resumeMission(DownloadMission mission) {
if (!mission.running) {
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS);
mission.start();
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_RUNNING);
}
}
public void pauseMission(DownloadMission mission) {
if (mission.running) {
mission.setEnqueued(false);
mission.pause();
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
}
@ -335,7 +352,7 @@ public class DownloadManager {
int count = 0;
synchronized (this) {
for (DownloadMission mission : mMissionsPending) {
if (mission.running && !mission.isFinished() && !mission.isPsFailed())
if (mission.running && !mission.isPsFailed() && !mission.isFinished())
count++;
}
}
@ -343,10 +360,36 @@ public class DownloadManager {
return count;
}
void pauseAllMissions() {
public void pauseAllMissions(boolean force) {
boolean flag = false;
synchronized (this) {
for (DownloadMission mission : mMissionsPending) mission.pause();
for (DownloadMission mission : mMissionsPending) {
if (!mission.running || mission.isPsRunning() || mission.isFinished()) continue;
if (force) mission.threads = null;// avoid waiting for threads
mission.pause();
flag = true;
}
}
if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
}
public void startAllMissions() {
boolean flag = false;
synchronized (this) {
for (DownloadMission mission : mMissionsPending) {
if (mission.running || mission.isPsFailed() || mission.isFinished()) continue;
flag = true;
mission.start();
}
}
if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS);
}
@ -415,31 +458,35 @@ public class DownloadManager {
}
/**
* runs another mission in queue if possible
* runs one or multiple missions in from queue if possible
*
* @return true if exits pending missions running or a mission was started, otherwise, false
* @return true if one or multiple missions are running, otherwise, false
*/
boolean runAnotherMission() {
boolean runMissions() {
synchronized (this) {
if (mMissionsPending.size() < 1) return false;
int i = getRunningMissionsCount();
if (i > 0) return true;
if (!canDownloadInCurrentNetwork()) return false;
for (DownloadMission mission : mMissionsPending) {
if (!mission.running && mission.errCode == DownloadMission.ERROR_NOTHING && mission.enqueued) {
resumeMission(mission);
return true;
}
if (mPrefQueueLimit) {
for (DownloadMission mission : mMissionsPending)
if (!mission.isFinished() && mission.running) return true;
}
return false;
boolean flag = false;
for (DownloadMission mission : mMissionsPending) {
if (mission.running || !mission.enqueued || mission.isFinished()) continue;
resumeMission(mission);
if (mPrefQueueLimit) return true;
flag = true;
}
return flag;
}
}
public MissionIterator getIterator() {
mSelfMissionsControl = true;
return new MissionIterator();
}
@ -457,31 +504,43 @@ public class DownloadManager {
private boolean canDownloadInCurrentNetwork() {
if (mLastNetworkStatus == NetworkState.Unavailable) return false;
return !(mPrefCrossNetwork && mLastNetworkStatus == NetworkState.MobileOperating);
return !(mPrefMeteredDownloads && mLastNetworkStatus == NetworkState.MeteredOperating);
}
void handleConnectivityChange(NetworkState currentStatus) {
void handleConnectivityState(NetworkState currentStatus, boolean updateOnly) {
if (currentStatus == mLastNetworkStatus) return;
mLastNetworkStatus = currentStatus;
if (currentStatus == NetworkState.Unavailable) return;
if (currentStatus == NetworkState.Unavailable) {
return;
} else if (currentStatus != NetworkState.MobileOperating || !mPrefCrossNetwork) {
return;
if (!mSelfMissionsControl || updateOnly) {
return;// don't touch anything without the user interaction
}
boolean flag = false;
boolean isMetered = mPrefMeteredDownloads && mLastNetworkStatus == NetworkState.MeteredOperating;
int running = 0;
int paused = 0;
synchronized (this) {
for (DownloadMission mission : mMissionsPending) {
if (mission.running && !mission.isFinished() && !mission.isPsRunning()) {
flag = true;
if (mission.isFinished() || mission.isPsRunning()) continue;
if (mission.running && isMetered) {
paused++;
mission.pause();
} else if (!mission.running && !isMetered && mission.enqueued) {
running++;
mission.start();
if (mPrefQueueLimit) break;
}
}
}
if (flag) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
if (running > 0) {
mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PROGRESS);
return;
}
if (paused > 0) mHandler.sendEmptyMessage(DownloadManagerService.MESSAGE_PAUSED);
}
void updateMaximumAttempts() {
@ -506,21 +565,24 @@ public class DownloadManager {
), Toast.LENGTH_LONG).show();
}
void checkForRunningMission(String location, String name, DownloadManagerService.DMChecker check) {
boolean listed;
boolean finished = false;
void checkForRunningMission(String location, String name, DMChecker check) {
MissionCheck result = MissionCheck.None;
synchronized (this) {
DownloadMission mission = getPendingMission(location, name);
if (mission != null) {
listed = true;
DownloadMission pending = getPendingMission(location, name);
if (pending == null) {
if (getFinishedMissionIndex(location, name) >= 0) result = MissionCheck.Finished;
} else {
listed = getFinishedMissionIndex(location, name) >= 0;
finished = listed;
if (pending.isFinished()) {
result = MissionCheck.Finished;// this never should happen (race-condition)
} else {
result = pending.running ? MissionCheck.PendingRunning : MissionCheck.Pending;
}
}
}
check.callback(listed, finished);
check.callback(result);
}
public class MissionIterator extends DiffUtil.Callback {
@ -592,39 +654,6 @@ public class DownloadManager {
return SPECIAL_NOTHING;
}
public MissionItem getItemUnsafe(int position) {
synchronized (DownloadManager.this) {
int count = mMissionsPending.size();
int count2 = mMissionsFinished.size();
if (count > 0) {
position--;
if (position == -1)
return new MissionItem(SPECIAL_PENDING);
else if (position < count)
return new MissionItem(SPECIAL_NOTHING, mMissionsPending.get(position));
else if (position == count && count2 > 0)
return new MissionItem(SPECIAL_FINISHED);
else
position -= count;
} else {
if (count2 > 0 && position == 0) {
return new MissionItem(SPECIAL_FINISHED);
}
}
position--;
if (count2 < 1) {
throw new RuntimeException(
String.format("Out of range. pending_count=%s finished_count=%s position=%s", count, count2, position)
);
}
return new MissionItem(SPECIAL_NOTHING, mMissionsFinished.get(position));
}
}
public void start() {
current = getSpecialItems();
@ -647,6 +676,32 @@ public class DownloadManager {
return hasFinished;
}
/**
* Check if exists missions running and paused. Corrupted and hidden missions are not counted
*
* @return two-dimensional array contains the current missions state.
* 1° entry: true if has at least one mission running
* 2° entry: true if has at least one mission paused
*/
public boolean[] hasValidPendingMissions() {
boolean running = false;
boolean paused = false;
synchronized (DownloadManager.this) {
for (DownloadMission mission : mMissionsPending) {
if (hidden.contains(mission) || mission.isPsFailed() || mission.isFinished())
continue;
if (mission.running)
paused = true;
else
running = true;
}
}
return new boolean[]{running, paused};
}
@Override
public int getOldListSize() {