support deleting files from the app and specify upload params
This commit is contained in:
@@ -208,4 +208,64 @@ public class UploadController {
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file from the server using its upload code
|
||||
*
|
||||
* @param uploadCode The upload code of the file to delete
|
||||
* @return CompletableFuture containing a boolean indicating success or failure
|
||||
*/
|
||||
public CompletableFuture<Boolean> deleteFileFromServer(String uploadCode) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
|
||||
if (uploadCode == null || uploadCode.isEmpty()) {
|
||||
Log.e(TAG, "Cannot delete file with empty upload code");
|
||||
future.complete(false);
|
||||
return future;
|
||||
}
|
||||
|
||||
// Create OkHttpClient with timeout configuration
|
||||
OkHttpClient client = new OkHttpClient.Builder()
|
||||
.connectTimeout(NetworkConstants.CONNECTION_TIMEOUT, TimeUnit.SECONDS)
|
||||
.readTimeout(NetworkConstants.READ_TIMEOUT, TimeUnit.SECONDS)
|
||||
.retryOnConnectionFailure(true)
|
||||
.build();
|
||||
|
||||
// Get server base URL and client key from preferences
|
||||
String serverBaseUrl = PreferencesUtil.getServerBaseUrl(context);
|
||||
String clientKey = PreferencesUtil.getClientKey(context);
|
||||
|
||||
// Create the delete URL
|
||||
String deleteUrl = serverBaseUrl + "/delete/" + uploadCode;
|
||||
Log.d(TAG, "Deleting file from server: " + deleteUrl);
|
||||
|
||||
// Build request with client-key header
|
||||
Request request = new Request.Builder()
|
||||
.url(deleteUrl)
|
||||
.header(NetworkConstants.CLIENT_KEY_HEADER, clientKey)
|
||||
.delete()
|
||||
.build();
|
||||
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onFailure(Call call, IOException e) {
|
||||
Log.e(TAG, "Server delete failed", e);
|
||||
future.complete(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResponse(Call call, Response response) throws IOException {
|
||||
if (response.isSuccessful()) {
|
||||
Log.d(TAG, "Server delete successful for code: " + uploadCode);
|
||||
future.complete(true);
|
||||
} else {
|
||||
String errorBody = response.body().string();
|
||||
Log.e(TAG, "Server delete failed with code: " + response.code() + ", Body: " + errorBody);
|
||||
future.complete(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ public interface UploadHistoryDao {
|
||||
@Query("SELECT * FROM upload_history ORDER BY upload_time DESC")
|
||||
LiveData<List<UploadHistory>> getAllHistory();
|
||||
|
||||
/**
|
||||
* Get all history entries ordered by upload time (most recent first) as a direct list
|
||||
*
|
||||
* @return List of history entries
|
||||
*/
|
||||
@Query("SELECT * FROM upload_history ORDER BY upload_time DESC")
|
||||
List<UploadHistory> getAllHistoryDirect();
|
||||
|
||||
/**
|
||||
* Get a history entry by ID
|
||||
*
|
||||
|
||||
@@ -36,6 +36,17 @@ public class UploadHistoryRepository {
|
||||
return mAllHistory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all history entries as a one-time operation
|
||||
*
|
||||
* @return CompletableFuture containing a list of all history entries
|
||||
*/
|
||||
public CompletableFuture<List<UploadHistory>> getAllHistoryOneTime() {
|
||||
return CompletableFuture.supplyAsync(() ->
|
||||
mUploadHistoryDao.getAllHistoryDirect(),
|
||||
AppDatabase.databaseWriteExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new history entry asynchronously
|
||||
*
|
||||
|
||||
@@ -60,6 +60,15 @@ public class FileUploadForegroundService extends Service {
|
||||
int maxDownloads = intent.getIntExtra(EXTRA_MAX_DOWNLOADS, NetworkConstants.DEFAULT_MAX_DOWNLOADS);
|
||||
int expiryHours = intent.getIntExtra(EXTRA_EXPIRY_HOURS, NetworkConstants.DEFAULT_EXPIRY_HOURS);
|
||||
|
||||
// Log parameters for debugging
|
||||
Log.d(TAG, "Upload parameters - Max Downloads: " + maxDownloads + ", Expiry Hours: " + expiryHours);
|
||||
|
||||
// Validate expiry hours
|
||||
if (expiryHours > NetworkConstants.MAX_EXPIRY_HOURS) {
|
||||
Log.w(TAG, "Expiry hours exceeded maximum allowed value, capping at " + NetworkConstants.MAX_EXPIRY_HOURS);
|
||||
expiryHours = NetworkConstants.MAX_EXPIRY_HOURS;
|
||||
}
|
||||
|
||||
if (filePath == null) {
|
||||
Log.e(TAG, "No file path provided");
|
||||
// Try to find the most recent dump state file
|
||||
@@ -91,12 +100,16 @@ public class FileUploadForegroundService extends Service {
|
||||
startForeground(NOTIFICATION_ID, createInitialNotification());
|
||||
}
|
||||
|
||||
// Create final copies for use in the callback
|
||||
final int finalMaxDownloads = maxDownloads;
|
||||
final int finalExpiryHours = expiryHours;
|
||||
|
||||
// Prepare file for upload
|
||||
FileController.prepareFileForUpload(file, new FileController.FilePrepareCallback() {
|
||||
@Override
|
||||
public void onSuccess(File preparedFile) {
|
||||
Log.d(TAG, "File prepared for upload: " + preparedFile.getName());
|
||||
uploadFile(preparedFile, maxDownloads, expiryHours);
|
||||
uploadFile(preparedFile, finalMaxDownloads, finalExpiryHours);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -91,6 +91,18 @@ public class PreferencesUtil {
|
||||
getPreferences(context).edit().putString(KEY_CLIENT_KEY, clientKey).apply();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the server base URL (address + port) without any path
|
||||
*
|
||||
* @param context The application context
|
||||
* @return The server base URL
|
||||
*/
|
||||
public static String getServerBaseUrl(Context context) {
|
||||
String address = getServerAddress(context);
|
||||
int port = getServerPort(context);
|
||||
return "http://" + address + ":" + port;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the complete server URL (address + port + path)
|
||||
*
|
||||
@@ -98,9 +110,7 @@ public class PreferencesUtil {
|
||||
* @return The complete server URL
|
||||
*/
|
||||
public static String getServerUrl(Context context) {
|
||||
String address = getServerAddress(context);
|
||||
int port = getServerPort(context);
|
||||
return "http://" + address + ":" + port + "/upload";
|
||||
return getServerBaseUrl(context) + "/upload";
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -252,34 +252,45 @@ public class MainActivity extends AppCompatActivity {
|
||||
List<File> selectedFiles = dumpstateAdapter.getSelectedFiles();
|
||||
int fileCount = selectedFiles.size();
|
||||
|
||||
// Update UI
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
uploadDumpstateButton.setEnabled(false);
|
||||
dumpstateStatusText.setText(getString(R.string.preparing_multiple_files, fileCount));
|
||||
|
||||
// Prepare and upload multiple files
|
||||
FileController.prepareMultipleFilesForUploadAsync(selectedFiles, this)
|
||||
.thenAccept(zipFile -> {
|
||||
Log.d(TAG, "Multiple files zipped successfully: " + zipFile.getAbsolutePath());
|
||||
|
||||
runOnUiThread(() -> {
|
||||
dumpstateStatusText.setText(getString(R.string.uploading_multiple_files, fileCount));
|
||||
});
|
||||
|
||||
// Upload the zip file
|
||||
startUploadService(zipFile);
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
Log.e(TAG, "Error preparing multiple files", ex);
|
||||
runOnUiThread(() -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
uploadDumpstateButton.setEnabled(true);
|
||||
Toast.makeText(MainActivity.this,
|
||||
getString(R.string.file_error, ex.getMessage()),
|
||||
Toast.LENGTH_LONG).show();
|
||||
});
|
||||
return null;
|
||||
});
|
||||
// Show upload parameters dialog first
|
||||
new UploadParamsDialog(this, new UploadParamsDialog.UploadParamsListener() {
|
||||
@Override
|
||||
public void onUploadParamsSet(int maxDownloads, int expiryHours) {
|
||||
// Update UI
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
uploadDumpstateButton.setEnabled(false);
|
||||
dumpstateStatusText.setText(getString(R.string.preparing_multiple_files, fileCount));
|
||||
|
||||
// Prepare and upload multiple files
|
||||
FileController.prepareMultipleFilesForUploadAsync(selectedFiles, MainActivity.this)
|
||||
.thenAccept(zipFile -> {
|
||||
Log.d(TAG, "Multiple files zipped successfully: " + zipFile.getAbsolutePath());
|
||||
|
||||
runOnUiThread(() -> {
|
||||
dumpstateStatusText.setText(getString(R.string.uploading_multiple_files, fileCount));
|
||||
});
|
||||
|
||||
// Upload the zip file with parameters
|
||||
startUploadService(zipFile, maxDownloads, expiryHours);
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
Log.e(TAG, "Error preparing multiple files", ex);
|
||||
runOnUiThread(() -> {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
uploadDumpstateButton.setEnabled(true);
|
||||
Toast.makeText(MainActivity.this,
|
||||
getString(R.string.file_error, ex.getMessage()),
|
||||
Toast.LENGTH_LONG).show();
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadParamsCancelled() {
|
||||
// User cancelled, do nothing
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -389,13 +400,8 @@ public class MainActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update UI
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
uploadSelectedFileButton.setEnabled(false);
|
||||
selectedFileText.setText(getString(R.string.uploading_file, selectedFile.getName()));
|
||||
|
||||
// Start the upload service
|
||||
startUploadService(selectedFile);
|
||||
// Show upload parameters dialog
|
||||
showUploadParamsDialog(selectedFile);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -413,7 +419,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
if (dumpstateFiles.length == 1) {
|
||||
// Only one dumpstate file, use it directly
|
||||
dumpstateFile = dumpstateFiles[0];
|
||||
startDumpstateUpload(dumpstateFile);
|
||||
showUploadParamsDialog(dumpstateFile);
|
||||
} else {
|
||||
// Multiple dumpstate files are available, suggest expanding the list
|
||||
if (!isDumpstateListExpanded) {
|
||||
@@ -421,7 +427,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
} else {
|
||||
// If list is already expanded, use the first file
|
||||
dumpstateFile = dumpstateFiles[0];
|
||||
startDumpstateUpload(dumpstateFile);
|
||||
showUploadParamsDialog(dumpstateFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -430,8 +436,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
* Start uploading the selected dumpstate file
|
||||
*
|
||||
* @param file The dumpstate file to upload
|
||||
* @param maxDownloads The maximum number of downloads
|
||||
* @param expiryHours The expiry time in hours
|
||||
*/
|
||||
private void startDumpstateUpload(File file) {
|
||||
private void startDumpstateUpload(File file, int maxDownloads, int expiryHours) {
|
||||
if (file == null || !file.exists()) {
|
||||
Toast.makeText(this, R.string.no_dumpstate_found, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
@@ -442,22 +450,57 @@ public class MainActivity extends AppCompatActivity {
|
||||
uploadDumpstateButton.setEnabled(false);
|
||||
dumpstateStatusText.setText(getString(R.string.uploading_file, file.getName()));
|
||||
|
||||
// Start the upload service
|
||||
startUploadService(file);
|
||||
// Start the upload service with parameters
|
||||
startUploadService(file, maxDownloads, expiryHours);
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the upload service with the given file
|
||||
* Start the upload service with the given file and parameters
|
||||
*
|
||||
* @param file The file to upload
|
||||
* @param maxDownloads The maximum number of downloads
|
||||
* @param expiryHours The expiry time in hours
|
||||
*/
|
||||
private void startUploadService(File file) {
|
||||
private void startUploadService(File file, int maxDownloads, int expiryHours) {
|
||||
// Start the upload service
|
||||
Intent serviceIntent = new Intent(this, FileUploadForegroundService.class);
|
||||
serviceIntent.putExtra(FileUploadForegroundService.EXTRA_FILE_PATH, file.getAbsolutePath());
|
||||
serviceIntent.putExtra(FileUploadForegroundService.EXTRA_MAX_DOWNLOADS, maxDownloads);
|
||||
serviceIntent.putExtra(FileUploadForegroundService.EXTRA_EXPIRY_HOURS, expiryHours);
|
||||
startService(serviceIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the upload parameters dialog
|
||||
*
|
||||
* @param file The file to upload
|
||||
*/
|
||||
private void showUploadParamsDialog(File file) {
|
||||
new UploadParamsDialog(this, new UploadParamsDialog.UploadParamsListener() {
|
||||
@Override
|
||||
public void onUploadParamsSet(int maxDownloads, int expiryHours) {
|
||||
// Update UI
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
uploadSelectedFileButton.setEnabled(false);
|
||||
|
||||
if (file == selectedFile) {
|
||||
selectedFileText.setText(getString(R.string.uploading_file, file.getName()));
|
||||
} else if (file == dumpstateFile) {
|
||||
dumpstateStatusText.setText(getString(R.string.uploading_file, file.getName()));
|
||||
uploadDumpstateButton.setEnabled(false);
|
||||
}
|
||||
|
||||
// Start the upload service with parameters
|
||||
startUploadService(file, maxDownloads, expiryHours);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUploadParamsCancelled() {
|
||||
// User cancelled, do nothing
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the selected file UI
|
||||
*
|
||||
|
||||
@@ -10,6 +10,8 @@ import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -73,6 +75,18 @@ public class UploadHistoryActivity extends AppCompatActivity implements UploadHi
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Observe the delete status to provide feedback
|
||||
viewModel.getDeleteStatus().observe(this, deleteSuccess -> {
|
||||
if (deleteSuccess != null) {
|
||||
if (deleteSuccess) {
|
||||
Toast.makeText(this, "File deleted from server successfully", Toast.LENGTH_SHORT).show();
|
||||
} else {
|
||||
Toast.makeText(this, "Failed to delete file from server, but removed from local history",
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -83,8 +97,30 @@ public class UploadHistoryActivity extends AppCompatActivity implements UploadHi
|
||||
finish();
|
||||
return true;
|
||||
} else if (id == R.id.action_clear_history) {
|
||||
viewModel.deleteAll();
|
||||
Toast.makeText(this, "History cleared", Toast.LENGTH_SHORT).show();
|
||||
// Show confirmation dialog
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Clear All History")
|
||||
.setMessage("Do you want to delete all uploads from both local history and the server?")
|
||||
.setPositiveButton("Yes", (dialog, which) -> {
|
||||
// Show progress dialog
|
||||
AlertDialog progressDialog = new AlertDialog.Builder(this)
|
||||
.setTitle("Deleting...")
|
||||
.setMessage("Attempting to delete all files from server and clear history.")
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
progressDialog.show();
|
||||
|
||||
// Attempt to delete all
|
||||
viewModel.deleteAll()
|
||||
.thenAccept(unused -> {
|
||||
runOnUiThread(() -> {
|
||||
progressDialog.dismiss();
|
||||
// Toast message will be shown through the observer
|
||||
});
|
||||
});
|
||||
})
|
||||
.setNegativeButton("No", null)
|
||||
.show();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -98,7 +134,29 @@ public class UploadHistoryActivity extends AppCompatActivity implements UploadHi
|
||||
|
||||
@Override
|
||||
public void onDeleteClick(UploadHistory history) {
|
||||
viewModel.delete(history);
|
||||
// Show confirmation dialog
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Delete Upload")
|
||||
.setMessage("Do you want to delete this upload from both local history and the server?")
|
||||
.setPositiveButton("Yes", (dialog, which) -> {
|
||||
// Show progress dialog
|
||||
AlertDialog progressDialog = new AlertDialog.Builder(this)
|
||||
.setTitle("Deleting...")
|
||||
.setMessage("Attempting to delete from server and local history.")
|
||||
.setCancelable(false)
|
||||
.create();
|
||||
progressDialog.show();
|
||||
|
||||
// Attempt to delete
|
||||
viewModel.delete(history)
|
||||
.thenAccept(unused -> {
|
||||
runOnUiThread(() -> {
|
||||
progressDialog.dismiss();
|
||||
});
|
||||
});
|
||||
})
|
||||
.setNegativeButton("No", null)
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
package com.mattintech.simplelogupload.view;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.mattintech.simplelogupload.R;
|
||||
import com.mattintech.simplelogupload.constant.NetworkConstants;
|
||||
|
||||
/**
|
||||
* Dialog for setting upload parameters
|
||||
*/
|
||||
public class UploadParamsDialog extends Dialog {
|
||||
|
||||
private EditText maxDownloadsInput;
|
||||
private EditText expiryTimeInput;
|
||||
private final UploadParamsListener listener;
|
||||
|
||||
/**
|
||||
* Interface for upload parameter callbacks
|
||||
*/
|
||||
public interface UploadParamsListener {
|
||||
void onUploadParamsSet(int maxDownloads, int expiryHours);
|
||||
void onUploadParamsCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param context The context
|
||||
* @param listener Callback listener
|
||||
*/
|
||||
public UploadParamsDialog(@NonNull Context context, UploadParamsListener listener) {
|
||||
super(context);
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.dialog_upload_params);
|
||||
|
||||
// Initialize views
|
||||
maxDownloadsInput = findViewById(R.id.maxDownloadsInput);
|
||||
expiryTimeInput = findViewById(R.id.expiryTimeInput);
|
||||
|
||||
// Set default values as hints
|
||||
maxDownloadsInput.setHint("Default: " + NetworkConstants.DEFAULT_MAX_DOWNLOADS);
|
||||
expiryTimeInput.setHint("Default: " + NetworkConstants.DEFAULT_EXPIRY_HOURS);
|
||||
|
||||
// Set up buttons
|
||||
Button confirmButton = findViewById(R.id.confirmButton);
|
||||
Button cancelButton = findViewById(R.id.cancelButton);
|
||||
|
||||
confirmButton.setOnClickListener(v -> {
|
||||
int maxDownloads = getMaxDownloadsValue();
|
||||
int expiryHours = getExpiryHoursValue();
|
||||
|
||||
// Validate expiry time
|
||||
if (expiryHours > NetworkConstants.MAX_EXPIRY_HOURS) {
|
||||
expiryTimeInput.setError("Maximum expiry time is " +
|
||||
NetworkConstants.MAX_EXPIRY_HOURS + " hours");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate max downloads (must be non-negative)
|
||||
if (maxDownloads < 0) {
|
||||
maxDownloadsInput.setError("Downloads must be 0 or greater");
|
||||
return;
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener.onUploadParamsSet(maxDownloads, expiryHours);
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
|
||||
cancelButton.setOnClickListener(v -> {
|
||||
if (listener != null) {
|
||||
listener.onUploadParamsCancelled();
|
||||
}
|
||||
dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max downloads value from the input
|
||||
*
|
||||
* @return The max downloads value (default if not set)
|
||||
*/
|
||||
private int getMaxDownloadsValue() {
|
||||
if (TextUtils.isEmpty(maxDownloadsInput.getText())) {
|
||||
return NetworkConstants.DEFAULT_MAX_DOWNLOADS;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(maxDownloadsInput.getText().toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return NetworkConstants.DEFAULT_MAX_DOWNLOADS;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the expiry hours value from the input
|
||||
*
|
||||
* @return The expiry hours value (default if not set)
|
||||
*/
|
||||
private int getExpiryHoursValue() {
|
||||
if (TextUtils.isEmpty(expiryTimeInput.getText())) {
|
||||
return NetworkConstants.DEFAULT_EXPIRY_HOURS;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(expiryTimeInput.getText().toString());
|
||||
} catch (NumberFormatException e) {
|
||||
return NetworkConstants.DEFAULT_EXPIRY_HOURS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,11 +5,14 @@ import android.app.Application;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.mattintech.simplelogupload.controller.UploadController;
|
||||
import com.mattintech.simplelogupload.db.UploadHistoryRepository;
|
||||
import com.mattintech.simplelogupload.model.UploadHistory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* ViewModel for upload history
|
||||
@@ -17,7 +20,9 @@ import java.util.List;
|
||||
public class UploadHistoryViewModel extends AndroidViewModel {
|
||||
|
||||
private final UploadHistoryRepository repository;
|
||||
private final UploadController uploadController;
|
||||
private final LiveData<List<UploadHistory>> allHistory;
|
||||
private final MutableLiveData<Boolean> deleteStatus = new MutableLiveData<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -27,6 +32,7 @@ public class UploadHistoryViewModel extends AndroidViewModel {
|
||||
public UploadHistoryViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
repository = new UploadHistoryRepository(application);
|
||||
uploadController = new UploadController(application);
|
||||
allHistory = repository.getAllHistory();
|
||||
}
|
||||
|
||||
@@ -49,12 +55,42 @@ public class UploadHistoryViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a history entry
|
||||
* Get the delete status LiveData
|
||||
*
|
||||
* @return LiveData containing the delete status
|
||||
*/
|
||||
public LiveData<Boolean> getDeleteStatus() {
|
||||
return deleteStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a history entry and the associated file from the server
|
||||
*
|
||||
* @param history The entry to delete
|
||||
* @return CompletableFuture for the operation
|
||||
*/
|
||||
public void delete(UploadHistory history) {
|
||||
repository.delete(history);
|
||||
public CompletableFuture<Void> delete(UploadHistory history) {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
// First attempt to delete from server
|
||||
uploadController.deleteFileFromServer(history.getUploadCode())
|
||||
.thenAccept(success -> {
|
||||
// Update the status
|
||||
deleteStatus.postValue(success);
|
||||
|
||||
// Always delete from local database, even if server delete fails
|
||||
repository.delete(history);
|
||||
future.complete(null);
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
// On exception, still delete from local database
|
||||
deleteStatus.postValue(false);
|
||||
repository.delete(history);
|
||||
future.complete(null);
|
||||
return null;
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,9 +103,76 @@ public class UploadHistoryViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all history entries
|
||||
* Delete all history entries and attempt to delete all files from server
|
||||
*
|
||||
* @return CompletableFuture for the operation
|
||||
*/
|
||||
public void deleteAll() {
|
||||
repository.deleteAll();
|
||||
public CompletableFuture<Void> deleteAll() {
|
||||
CompletableFuture<Void> future = new CompletableFuture<>();
|
||||
|
||||
// First get all history entries
|
||||
repository.getAllHistoryOneTime()
|
||||
.thenAccept(historyList -> {
|
||||
if (historyList == null || historyList.isEmpty()) {
|
||||
// No entries to delete
|
||||
repository.deleteAll();
|
||||
future.complete(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Counter for tracking completion
|
||||
final int[] completedCount = {0};
|
||||
final int totalCount = historyList.size();
|
||||
final boolean[] anyServerSuccess = {false};
|
||||
|
||||
// Process each entry
|
||||
for (UploadHistory history : historyList) {
|
||||
uploadController.deleteFileFromServer(history.getUploadCode())
|
||||
.thenAccept(success -> {
|
||||
if (success) {
|
||||
anyServerSuccess[0] = true;
|
||||
}
|
||||
|
||||
// Increment counter
|
||||
completedCount[0]++;
|
||||
|
||||
// Check if all operations are complete
|
||||
if (completedCount[0] >= totalCount) {
|
||||
// All server delete attempts completed, now delete local entries
|
||||
repository.deleteAll();
|
||||
|
||||
// Update status based on overall success
|
||||
deleteStatus.postValue(anyServerSuccess[0]);
|
||||
|
||||
future.complete(null);
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
// Count failed operations too
|
||||
completedCount[0]++;
|
||||
|
||||
// Check if all operations are complete
|
||||
if (completedCount[0] >= totalCount) {
|
||||
// All server delete attempts completed, now delete local entries
|
||||
repository.deleteAll();
|
||||
|
||||
// Update status based on overall success
|
||||
deleteStatus.postValue(anyServerSuccess[0]);
|
||||
|
||||
future.complete(null);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
})
|
||||
.exceptionally(ex -> {
|
||||
// If we can't get the history, just delete local entries
|
||||
repository.deleteAll();
|
||||
deleteStatus.postValue(false);
|
||||
future.complete(null);
|
||||
return null;
|
||||
});
|
||||
|
||||
return future;
|
||||
}
|
||||
}
|
||||
|
||||
97
app/src/main/res/layout/dialog_upload_params.xml
Normal file
97
app/src/main/res/layout/dialog_upload_params.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/upload_params_title"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/max_downloads_label"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/maxDownloadsInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="number"
|
||||
android:hint="@string/max_downloads_hint"
|
||||
android:importantForAutofill="no" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/max_downloads_note"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginBottom="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/expiry_time_label"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/expiryTimeInput"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:inputType="number"
|
||||
android:hint="@string/expiry_time_hint"
|
||||
android:importantForAutofill="no" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/expiry_time_note"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="italic"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="end">
|
||||
|
||||
<Button
|
||||
android:id="@+id/cancelButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/cancel_button"
|
||||
style="@style/Widget.AppCompat.Button.Borderless" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/confirmButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/upload_button" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
@@ -56,4 +56,15 @@
|
||||
<string name="no_dumpstates_selected">No files selected</string>
|
||||
<string name="preparing_multiple_files">Preparing %1$d files for upload…</string>
|
||||
<string name="uploading_multiple_files">Uploading %1$d files as one package</string>
|
||||
|
||||
<!-- Upload Parameters Dialog -->
|
||||
<string name="upload_params_title">Upload Parameters</string>
|
||||
<string name="max_downloads_label">Maximum Downloads:</string>
|
||||
<string name="max_downloads_hint">Default: 1</string>
|
||||
<string name="max_downloads_note">Set to 0 for unlimited downloads</string>
|
||||
<string name="expiry_time_label">Expiry Time (hours):</string>
|
||||
<string name="expiry_time_hint">Default: 24</string>
|
||||
<string name="expiry_time_note">Maximum 24 hours (1 day)</string>
|
||||
<string name="cancel_button">Cancel</string>
|
||||
<string name="upload_button">Upload</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user