tempus/app/src/main/java/com/cappielloantonio/tempo/util/ExternalAudioWriter.java

193 lines
8.4 KiB
Java

package com.cappielloantonio.tempo.util;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.Settings;
import android.webkit.MimeTypeMap;
import androidx.core.app.NotificationCompat;
import androidx.documentfile.provider.DocumentFile;
import androidx.media3.common.MediaItem;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.ExternalAudioReader;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.Normalizer;
import java.util.Locale;
public class ExternalAudioWriter {
private static String sanitizeFileName(String name) {
String sanitized = name.replaceAll("[\\/:*?\\\"<>|]", "_");
sanitized = sanitized.replaceAll("\\s+", " ").trim();
return sanitized;
}
private static String normalizeForComparison(String name) {
String s = sanitizeFileName(name);
s = Normalizer.normalize(s, Normalizer.Form.NFKD);
s = s.replaceAll("\\p{InCombiningDiacriticalMarks}+", "");
return s.toLowerCase(Locale.ROOT);
}
private static DocumentFile findFile(DocumentFile dir, String fileName) {
String normalized = normalizeForComparison(fileName);
for (DocumentFile file : dir.listFiles()) {
String existing = file.getName();
if (existing != null && normalizeForComparison(existing).equals(normalized)) {
return file;
}
}
return null;
}
public static void downloadToUserDirectory(Context context, MediaItem mediaItem, String fallbackName) {
new Thread(() -> {
String uriString = Preferences.getDownloadDirectoryUri();
if (uriString == null) {
notifyUnavailable(context);
return;
}
Uri treeUri = Uri.parse(uriString);
DocumentFile directory = DocumentFile.fromTreeUri(context, treeUri);
if (directory == null || !directory.canWrite()) {
notifyFailure(context, "Cannot write to folder.");
return;
}
try {
Uri mediaUri = mediaItem.requestMetadata.mediaUri;
if (mediaUri == null) {
notifyFailure(context, "Invalid media URI.");
return;
}
String scheme = mediaUri.getScheme();
if (scheme == null || (!scheme.equals("http") && !scheme.equals("https"))) {
notifyExists(context, fallbackName);
return;
}
HttpURLConnection connection = (HttpURLConnection) new URL(mediaUri.toString()).openConnection();
connection.connect();
String mimeType = connection.getContentType();
if (mimeType == null) mimeType = "application/octet-stream";
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType);
if (extension == null) extension = "bin";
String artist = mediaItem.mediaMetadata.artist != null ? mediaItem.mediaMetadata.artist.toString() : "";
String title = mediaItem.mediaMetadata.title != null ? mediaItem.mediaMetadata.title.toString() : fallbackName;
String album = mediaItem.mediaMetadata.albumTitle != null ? mediaItem.mediaMetadata.albumTitle.toString() : "";
String name = artist.isEmpty() ? title : artist + " - " + title;
if (!album.isEmpty()) name += " (" + album + ")";
String sanitized = sanitizeFileName(name);
String fullName = sanitized + "." + extension;
DocumentFile existingFile = findFile(directory, fullName);
long remoteLength = connection.getContentLengthLong();
if (existingFile != null && existingFile.exists()) {
long localLength = existingFile.length();
if (remoteLength > 0 && localLength == remoteLength) {
notifyExists(context, fullName);
return;
} else {
existingFile.delete();
}
}
DocumentFile targetFile = directory.createFile(mimeType, fullName);
if (targetFile == null) {
notifyFailure(context, "Failed to create file.");
return;
}
try (InputStream in = connection.getInputStream();
OutputStream out = context.getContentResolver().openOutputStream(targetFile.getUri())) {
if (out == null) {
notifyFailure(context, "Cannot open output stream.");
return;
}
byte[] buffer = new byte[8192];
int len;
long total = 0;
while ((len = in.read(buffer)) != -1) {
out.write(buffer, 0, len);
total += len;
}
if (remoteLength > 0 && total != remoteLength) {
targetFile.delete();
notifyFailure(context, "Incomplete download.");
} else {
notifySuccess(context, fullName);
ExternalAudioReader.refreshCache();
}
}
} catch (Exception e) {
notifyFailure(context, e.getMessage());
}
}).start();
}
private static void notifyUnavailable(Context context) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Intent settingsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
Uri.fromParts("package", context.getPackageName(), null));
PendingIntent openSettings = PendingIntent.getActivity(context, 0, settingsIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
.setContentTitle("No download folder set")
.setContentText("Tap to set one in settings")
.setSmallIcon(android.R.drawable.stat_notify_error)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSilent(true)
.setContentIntent(openSettings)
.setAutoCancel(true);
manager.notify(1011, builder.build());
}
private static void notifyFailure(Context context, String message) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Download failed")
.setContentText(message)
.setSmallIcon(android.R.drawable.stat_notify_error)
.setAutoCancel(true);
manager.notify((int) System.currentTimeMillis(), builder.build());
}
private static void notifySuccess(Context context, String name) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Download complete")
.setContentText(name)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setAutoCancel(true);
manager.notify((int) System.currentTimeMillis(), builder.build());
}
private static void notifyExists(Context context, String name) {
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID)
.setContentTitle("Already downloaded")
.setContentText(name)
.setSmallIcon(android.R.drawable.stat_sys_warning)
.setAutoCancel(true);
manager.notify((int) System.currentTimeMillis(), builder.build());
}
}