diff --git a/app/build.gradle b/app/build.gradle index f7df93296..b42873acc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId 'com.seafile.seadroid2' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 123 - versionName "2.2.48" + versionCode 125 + versionName "2.2.50" multiDexEnabled true resValue "string", "authorities", applicationId + '.cameraupload.provider' resValue "string", "account_type", "com.seafile.seadroid2.account.api2" @@ -124,11 +124,17 @@ android { implementation 'com.shuyu:gsyVideoPlayer-java:3.0.0' implementation 'com.shuyu:gsyVideoPlayer-ex_so:3.0.0' implementation 'com.squareup.okhttp3:okhttp:3.9.1' + implementation 'com.github.getActivity:XXPermissions:16.2' + implementation 'io.reactivex.rxjava3:rxjava:3.1.5' + implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' + implementation 'com.google.code.gson:gson:2.10' + + implementation 'com.yydcdut:markdown-processor:0.1.3' implementation 'ren.qinc.edit:lib:0.0.5'//editor undo redo implementation 'com.github.tiagohm.MarkdownView:library:0.19.0' - implementation 'org.greenrobot:eventbus:3.1.1' + implementation 'org.greenrobot:eventbus:3.3.1' implementation 'com.github.bumptech.glide:glide:4.9.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.9.0' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 62f41589c..d6f0b8fa7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,7 +36,10 @@ + + + - - + @@ -159,6 +162,9 @@ + + @@ -192,6 +198,7 @@ + diff --git a/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java b/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java index 929cd4c8e..504e28adf 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java +++ b/app/src/main/java/com/seafile/seadroid2/SeadroidApplication.java @@ -18,6 +18,7 @@ import com.seafile.seadroid2.data.StorageManager; import com.seafile.seadroid2.gesturelock.AppLockManager; import com.seafile.seadroid2.ui.CustomNotificationBuilder; +import com.seafile.seadroid2.util.CrashHandler; import com.seafile.seadroid2.util.Utils; import java.io.File; @@ -28,6 +29,8 @@ public class SeadroidApplication extends Application { private int totalNumber; private int scanUploadStatus; private static SeadroidApplication instance; + private int totalBackup; + private int waitingBackup; public void onCreate() { super.onCreate(); @@ -40,7 +43,8 @@ public void onCreate() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { initNotificationChannel(); } - + CrashHandler crashHandler = CrashHandler.getInstance(); + crashHandler.init(this); Utils.logPhoneModelInfo(); } @@ -125,4 +129,16 @@ public int getScanUploadStatus() { return scanUploadStatus; } + public int getTotalBackup() { + return totalBackup; + } + + public int getWaitingBackup() { + return waitingBackup; + } + + public void setFolderBackupNumber(int totalBackup, int waitingBackup) { + this.totalBackup = totalBackup; + this.waitingBackup = waitingBackup; + } } diff --git a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java index 5262f95ad..9fdb8437e 100644 --- a/app/src/main/java/com/seafile/seadroid2/SeafConnection.java +++ b/app/src/main/java/com/seafile/seadroid2/SeafConnection.java @@ -880,8 +880,8 @@ private String commitUpload(String link, List blkIds, String dir, String */ public String uploadFile(String repoID, String dir, String filePath, ProgressMonitor monitor, boolean update) throws SeafException, IOException { - String url = getUploadLink(repoID, update, dir); - return uploadFileCommon(url, repoID, dir, filePath, monitor, update); + String url = getUploadLink(repoID, update, "/"); + return uploadFileCommon(url, repoID, dir, filePath, monitor, update); } @@ -903,7 +903,12 @@ private String uploadFileCommon(String link, String repoID, String dir, String targetFilePath = Utils.pathJoin(dir, file.getName()); builder.addFormDataPart("target_file", targetFilePath); } else { - builder.addFormDataPart("parent_dir", dir); + builder.addFormDataPart("parent_dir", "/"); + if (dir.charAt(0) == '/') { + dir = dir.substring(1); + dir = dir + "/"; + } + builder.addFormDataPart("relative_path", dir); } builder.addFormDataPart("file", file.getName(), RequestManager.getInstance(account).createProgressRequestBody(monitor, file)); diff --git a/app/src/main/java/com/seafile/seadroid2/SettingsManager.java b/app/src/main/java/com/seafile/seadroid2/SettingsManager.java index 480a890f5..0bf5d199a 100644 --- a/app/src/main/java/com/seafile/seadroid2/SettingsManager.java +++ b/app/src/main/java/com/seafile/seadroid2/SettingsManager.java @@ -69,6 +69,7 @@ private SettingsManager() { public static final String CAMERA_UPLOAD_ADVANCED_SCREEN_KEY = "screen_camera_upload_advanced_feature"; public static final String CAMERA_UPLOAD_ADVANCED_CATEGORY_KEY = "category_camera_upload_advanced_key"; public static final String CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY = "allow_data_plan_switch_key"; + public static final String CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY = "allow_videos_upload_switch_key"; public static final String CAMERA_UPLOAD_BUCKETS_KEY = "camera_upload_buckets_key"; public static final String CAMERA_UPLOAD_CATEGORY_KEY = "category_camera_upload_key"; @@ -102,6 +103,18 @@ private SettingsManager() { public static final String PIC_CHECK_START = "pic_check_start"; public static final String UPLOAD_COMPLETED_TIME = "upload_completed_time"; + //FolderBackupStatus + public static final String FOLDER_BACKUP_SWITCH_KEY = "folder_backup_switch_key"; + public static final String FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY = "folder_backup_allow_data_plan_switch_key"; + public static final String FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY = "folder_automatic_backup_switch_key"; + public static final String FOLDER_BACKUP_ACCOUNT_EMAIL = "folder_backup_account_email"; + public static final String FOLDER_BACKUP_CATEGORY_KEY = "folder_backup_category_key"; + public static final String FOLDER_BACKUP_MODE = "folder_backup_mode"; + public static final String FOLDER_BACKUP_LIBRARY_KEY = "folder_backup_library_key"; + public static final String SELECTED_BACKUP_FOLDERS_KEY = "selected_backup_folders_key"; + public static final String FOLDER_BACKUP_STATE = "folder_backup_state"; + public static final String FOLDER_BACKUP_PATHS = "folder_backup_paths"; + public static long lock_timestamp = 0; public static final long LOCK_EXPIRATION_MSECS = 5 * 60 * 1000; @@ -233,6 +246,9 @@ public boolean checkCameraUploadNetworkAvailable() { public boolean isDataPlanAllowed() { return settingsSharedPref.getBoolean(CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY, false); } + public boolean isFolderBackupDataPlanAllowed() { + return settingsSharedPref.getBoolean(FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY, false); + } public boolean isVideosUploadAllowed() { return settingsSharedPref.getBoolean(CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY, false); @@ -241,6 +257,16 @@ public boolean isVideosUploadAllowed() { public void saveDataPlanAllowed(boolean isAllowed) { settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).commit(); } + public void saveFolderBackupDataPlanAllowed(boolean isAllowed) { + settingsSharedPref.edit().putBoolean(FOLDER_BACKUP_ALLOW_DATA_PLAN_SWITCH_KEY, isAllowed).commit(); + } + + public void saveFolderAutomaticBackup(boolean isAllowed) { + settingsSharedPref.edit().putBoolean(FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY, isAllowed).commit(); + } + public boolean isFolderAutomaticBackup() { + return settingsSharedPref.getBoolean(FOLDER_AUTOMATIC_BACKUP_SWITCH_KEY, false); + } public void saveVideosAllowed(boolean isVideosUploadAllowed) { settingsSharedPref.edit().putBoolean(CAMERA_UPLOAD_ALLOW_VIDEOS_SWITCH_KEY, isVideosUploadAllowed).commit(); @@ -310,4 +336,22 @@ public void savePrivacyPolicyConfirmed(int type) { public int getPrivacyPolicyConfirmed() { return sharedPref.getInt(PRIVACY_POLICY_CONFIRMED, 0); } + + public void saveBackupPaths(String path) { + editor.putString(FOLDER_BACKUP_PATHS, path); + editor.commit(); + } + + public String getBackupPaths() { + return sharedPref.getString(SettingsManager.FOLDER_BACKUP_PATHS, null); + } + + public void saveBackupEmail(String path) { + editor.putString(FOLDER_BACKUP_ACCOUNT_EMAIL, path); + editor.commit(); + } + + public String getBackupEmail() { + return sharedPref.getString(SettingsManager.FOLDER_BACKUP_ACCOUNT_EMAIL, null); + } } diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java index 66662f842..38f20e60d 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/BucketsFragment.java @@ -1,8 +1,12 @@ package com.seafile.seadroid2.cameraupload; +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.provider.MediaStore; import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -10,12 +14,23 @@ import android.view.animation.Animation; import android.view.animation.Animation.AnimationListener; import android.view.animation.TranslateAnimation; +import android.widget.BaseAdapter; import android.widget.Button; +import android.widget.GridView; +import android.widget.ImageView; import android.widget.RadioGroup; import android.widget.RadioGroup.OnCheckedChangeListener; -import android.widget.RelativeLayout; +import android.widget.TextView; + import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.GlideApp; +import com.seafile.seadroid2.util.Utils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; /** * Buckets fragment @@ -23,21 +38,21 @@ public class BucketsFragment extends Fragment { private CameraUploadConfigActivity mActivity; - private FragmentManager fm; - private BucketsSelectionFragment mSelectionFragment; private RadioGroup mRadioGroup; private Button mDoneBtn; private TranslateAnimation mSlideInAnimation; private TranslateAnimation mSlideOutAnimation; - private RelativeLayout mDirectoriesLayout; + private GridView mGridView; + private List buckets; + private boolean[] selectedBuckets; + private ImageAdapter imageAdapter; + private Bitmap[] thumbnails; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { mActivity = (CameraUploadConfigActivity) getActivity(); View rootView = mActivity.getLayoutInflater().inflate(R.layout.cuc_local_directory_fragment, null); - mDirectoriesLayout = (RelativeLayout) rootView.findViewById(R.id.cuc_local_directory_list_container); - mSlideInAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF, 2.0f, @@ -54,35 +69,68 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mSlideOutAnimation.setDuration(600); mSlideOutAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); mSlideOutAnimation.setAnimationListener(slideOutListener); - - fm = getChildFragmentManager(); - fm.beginTransaction() - .add(R.id.cuc_local_directory_list_container, getSelectionFragment()) - .commit(); - + mGridView = (GridView) rootView.findViewById(R.id.cuc_bucket_selection_grid); mRadioGroup = (RadioGroup) rootView.findViewById(R.id.cuc_local_directory_radio_group); mDoneBtn = (Button) rootView.findViewById(R.id.cuc_local_directory_btn); mDoneBtn.setOnClickListener(onClickListener); if (mActivity.isChooseDirPage()) mDoneBtn.setVisibility(View.VISIBLE); - // RadioButton mAutoScanRadioBtn = (RadioButton) mRadioGroup.findViewById(R.id.cuc_local_library_auto_scan_rb); - // RadioButton mCustomPickRadioBtn = (RadioButton) mRadioGroup.findViewById(R.id.cuc_local_library_pick_folders_rb); SettingsManager settingsManager = SettingsManager.instance(); - if (settingsManager.getCameraUploadBucketList().isEmpty()) { // auto scan - mDirectoriesLayout.setVisibility(View.INVISIBLE); - mDirectoriesLayout.setEnabled(false); + mGridView.setVisibility(View.INVISIBLE); + mGridView.setEnabled(false); mRadioGroup.check(R.id.cuc_local_directory_auto_scan_rb); } else { // pick custom folders to scan - mDirectoriesLayout.setVisibility(View.VISIBLE); - mDirectoriesLayout.setEnabled(true); + mGridView.setVisibility(View.VISIBLE); + mGridView.setEnabled(true); mRadioGroup.check(R.id.cuc_local_directory_pick_folders_rb); } mRadioGroup.setOnCheckedChangeListener(onCheckedChangeListener); + List currentBucketList = settingsManager.getCameraUploadBucketList(); + buckets = GalleryBucketUtils.getMediaBuckets(getActivity().getApplicationContext()); + selectedBuckets = new boolean[buckets.size()]; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + thumbnails = new Bitmap[buckets.size()]; + for (int i = 0; i < buckets.size(); i++) { + GalleryBucketUtils.Bucket b = buckets.get(i); + if (b.image_id > 0) { + thumbnails[i] = MediaStore.Images.Thumbnails.getThumbnail( + getActivity().getApplicationContext().getContentResolver(), b.image_id, + MediaStore.Images.Thumbnails.MINI_KIND, null); + } + if (currentBucketList.size() > 0) + selectedBuckets[i] = currentBucketList.contains(b.id); + else + selectedBuckets[i] = b.isCameraBucket; + } + } else { + for (int i = 0; i < this.buckets.size(); i++) { + GalleryBucketUtils.Bucket b = this.buckets.get(i); + if (b.isImages != null && b.isImages.equals(GalleryBucketUtils.IMAGES)) { + Uri image_uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, b.imageId); + String image_path = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), image_uri, "images"); + b.imagePath = image_path; + } else { + Uri video_uri = Uri.withAppendedPath(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, b.videoId); + String videoPath = Utils.getRealPathFromURI(SeadroidApplication.getAppContext(), video_uri, "video"); + b.videoPath = videoPath; + } + + // if the user has previously selected buckets, mark these. + // otherwise, select the ones that will be auto-guessed. + if (currentBucketList.size() > 0) + selectedBuckets[i] = currentBucketList.contains(b.id); + else + selectedBuckets[i] = b.isCameraBucket; + } + } + + imageAdapter = new ImageAdapter(); + mGridView.setAdapter(imageAdapter); return rootView; } @@ -96,14 +144,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa public void onCheckedChanged(RadioGroup radioGroup, int radioButtonId) { switch (radioButtonId) { case R.id.cuc_local_directory_auto_scan_rb: - mActivity.saveSettings(); - mDirectoriesLayout.startAnimation(mSlideOutAnimation); - mDirectoriesLayout.setEnabled(false); + mGridView.startAnimation(mSlideOutAnimation); + mGridView.setEnabled(false); mActivity.saveSettings(); break; case R.id.cuc_local_directory_pick_folders_rb: - mDirectoriesLayout.startAnimation(mSlideInAnimation); - mDirectoriesLayout.setEnabled(true); + mGridView.startAnimation(mSlideInAnimation); + mGridView.setEnabled(true); mActivity.saveSettings(); break; } @@ -131,7 +178,7 @@ public void onClick(View v) { @Override public void onAnimationEnd(Animation arg0) { - mDirectoriesLayout.setVisibility(View.INVISIBLE); + mGridView.setVisibility(View.INVISIBLE); } @Override @@ -139,7 +186,7 @@ public void onAnimationRepeat(Animation arg0) {} @Override public void onAnimationStart(Animation arg0) { - mDirectoriesLayout.setVisibility(View.VISIBLE); + mGridView.setVisibility(View.VISIBLE); } }; @@ -151,7 +198,7 @@ public void onAnimationStart(Animation arg0) { @Override public void onAnimationEnd(Animation arg0) { - mDirectoriesLayout.setVisibility(View.VISIBLE); + mGridView.setVisibility(View.VISIBLE); } @Override @@ -159,21 +206,90 @@ public void onAnimationRepeat(Animation arg0) {} @Override public void onAnimationStart(Animation arg0) { - mDirectoriesLayout.setVisibility(View.VISIBLE); + mGridView.setVisibility(View.VISIBLE); } }; - /** - * Instantiates a new fragment if mSelectionFragment is null. - * Returns the current fragment, otherwise. - */ - public BucketsSelectionFragment getSelectionFragment() { - if (mSelectionFragment == null) { - mSelectionFragment = new BucketsSelectionFragment(); + + public class ImageAdapter extends BaseAdapter { + private LayoutInflater mInflater; + + public ImageAdapter() { + mInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE); } - return mSelectionFragment; + public int getCount() { + return buckets.size(); + } + + public Object getItem(int position) { + return position; + } + + public long getItemId(int position) { + return position; + } + + public View getView(int position, View convertView, ViewGroup parent) { + final ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(); + convertView = mInflater.inflate(R.layout.bucket_item, null); + holder.imageview = (ImageView) convertView.findViewById(R.id.bucket_item_thumbImage); + holder.text = (TextView) convertView.findViewById(R.id.bucket_item_name); + holder.marking = (ImageView) convertView.findViewById(R.id.bucket_item_marking); + + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); + } + holder.imageview.setId(position); + holder.text.setText(buckets.get(position).name); + holder.imageview.setOnClickListener(new View.OnClickListener() { + + public void onClick(View v) { + int id = v.getId(); + selectedBuckets[id] = !selectedBuckets[id]; + if (selectedBuckets[id]) + holder.marking.setBackgroundResource(R.drawable.checkbox_checked); + else + holder.marking.setBackgroundResource(R.drawable.checkbox_unchecked); + } + }); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + holder.imageview.setImageBitmap(thumbnails[position]); + } else { + if (buckets.get(position).isImages != null && buckets.get(position).isImages.equals(GalleryBucketUtils.IMAGES)) { + GlideApp.with(getActivity()).load(buckets.get(position).imagePath).into(holder.imageview); + } else { + GlideApp.with(getActivity()).load(Uri.fromFile(new File(buckets.get(position).videoPath))).into(holder.imageview); + } + } + if (selectedBuckets[position]) + holder.marking.setBackgroundResource(R.drawable.checkbox_checked); + else + holder.marking.setBackgroundResource(R.drawable.checkbox_unchecked); + holder.id = position; + return convertView; + } + } + + static class ViewHolder { + ImageView imageview; + ImageView marking; + TextView text; + int id; } + public List getSelectedBuckets() { + List ret = new ArrayList<>(); + for (int i = 0; i < buckets.size(); i++) { + if (selectedBuckets[i]) { + ret.add(buckets.get(i).id); + } + } + + return ret; + } } diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java index d635705b3..a517e746c 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraSyncAdapter.java @@ -646,7 +646,7 @@ private void uploadFile(DataManager dataManager, File file, String bucketName) t // Log.d(DEBUG_TAG, "uploading file " + file.getName() + " to " + serverPath); Utils.utilsLogInfo(true,"====uploading file " + file.getName() + " to " + serverPath); - int taskID = txService.addUploadTask(dataManager.getAccount(), targetRepoId, targetRepoName, + int taskID = txService.addCameraUploadTask(dataManager.getAccount(), targetRepoId, targetRepoName, serverPath, file.getAbsolutePath(), false, false); tasksInProgress.add(taskID); } diff --git a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java index 7a9747a18..e077b6e74 100644 --- a/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/cameraupload/CameraUploadConfigActivity.java @@ -102,7 +102,7 @@ public void saveSettings() { if (isChooseBothPages || isChooseDirPage) { SettingsManager settingsManager = SettingsManager.instance(); - List selectedBuckets = mBucketsFragment.getSelectionFragment().getSelectedBuckets(); + List selectedBuckets = mBucketsFragment.getSelectedBuckets(); if (mBucketsFragment.isAutoScanSelected()) { selectedBuckets.clear(); } diff --git a/app/src/main/java/com/seafile/seadroid2/data/CameraSyncEvent.java b/app/src/main/java/com/seafile/seadroid2/data/CameraSyncEvent.java index dbb450242..e0c985713 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/CameraSyncEvent.java +++ b/app/src/main/java/com/seafile/seadroid2/data/CameraSyncEvent.java @@ -7,7 +7,6 @@ public CameraSyncEvent(String logInfo) { this.logInfo = logInfo; } - public String getLogInfo() { return logInfo; } diff --git a/app/src/main/java/com/seafile/seadroid2/data/DataManager.java b/app/src/main/java/com/seafile/seadroid2/data/DataManager.java index 74fa0036a..955f7e283 100644 --- a/app/src/main/java/com/seafile/seadroid2/data/DataManager.java +++ b/app/src/main/java/com/seafile/seadroid2/data/DataManager.java @@ -286,6 +286,9 @@ private synchronized String getRepoDir(String repoName, String repoID) throws Ru * @param path */ public File getLocalRepoFile(String repoName, String repoID, String path) throws RuntimeException { + if (TextUtils.isEmpty(repoID)) { + return null; + } String repoDir = getRepoDir(repoName, repoID); if (TextUtils.isEmpty(repoDir)) { return null; diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java new file mode 100644 index 000000000..3a5358d70 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/CloudLibraryChooserFragment.java @@ -0,0 +1,740 @@ +package com.seafile.seadroid2.folderbackup; + +import android.content.Intent; +import android.database.Cursor; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.AdapterView; +import android.widget.BaseAdapter; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.google.common.collect.Lists; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeafConnection; +import com.seafile.seadroid2.SeafException; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.avatar.Avatar; +import com.seafile.seadroid2.avatar.AvatarManager; +import com.seafile.seadroid2.cameraupload.CloudLibraryAccountAdapter; +import com.seafile.seadroid2.cameraupload.CloudLibraryAdapter; +import com.seafile.seadroid2.data.DataManager; +import com.seafile.seadroid2.data.SeafDirent; +import com.seafile.seadroid2.data.SeafRepo; +import com.seafile.seadroid2.ui.NavContext; +import com.seafile.seadroid2.ui.adapter.DirentsAdapter; +import com.seafile.seadroid2.ui.dialog.PasswordDialog; +import com.seafile.seadroid2.ui.dialog.TaskDialog; +import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.Utils; + +import java.net.HttpURLConnection; +import java.util.ArrayList; +import java.util.List; + +public class CloudLibraryChooserFragment extends Fragment { + + public static final String DEBUG_TAG = "CloudLibraryChooserFragment"; + private FolderBackupConfigActivity mActivity; + private Button mDoneBtn; + public static final String PASSWORD_DIALOG_FRAGMENT_TAG = "passwordDialogFragmentTag"; + public static final String ONLY_SHOW_WRITABLE_REPOS = "onlyShowWritableRepos"; + public static final String ENCRYPTED_REPO_ID = "encryptedRepoId"; + private static final int STEP_CHOOSE_ACCOUNT = 1; + private static final int STEP_CHOOSE_REPO = 2; + private static final int STEP_CHOOSE_DIR = 3; + private int mStep = 1; + private CloudLibraryAccountAdapter mAccountAdapter; + private CloudLibraryAdapter mReposAdapter; + private DirentsAdapter mDirentsAdapter; + private AccountManager mAccountManager; + private DataManager mDataManager; + private NavContext mNavContext; + private Account mAccount; + private LoadAccountsTask mLoadAccountsTask; + private LoadReposTask mLoadReposTask; + private LoadFolderTask mLoadFolderTask; + private AvatarManager avatarManager; + private RelativeLayout mUpLayout; + private TextView mCurrentFolderText; + private TextView mEmptyText, mErrorText; + private ImageView mRefreshBtn; + private View mProgressContainer, mListContainer; + private ListView mFoldersListView; + private Cursor mCursor; + private String mCurrentDir; + private boolean canChooseAccount; + private boolean onlyShowWritableRepos; + private String encryptedRepoId; + private boolean isOnlyChooseRepo; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + mActivity = (FolderBackupConfigActivity) getActivity(); + View rootView = mActivity.getLayoutInflater().inflate(R.layout.folder_backup_remote_library_fragment, null); + Intent intent = mActivity.getIntent(); + avatarManager = new AvatarManager(); + Account account = intent.getParcelableExtra("account"); + if (account == null) { + canChooseAccount = true; + } else { + mAccount = account; + } + onlyShowWritableRepos = intent.getBooleanExtra(ONLY_SHOW_WRITABLE_REPOS, true); + encryptedRepoId = intent.getStringExtra(ENCRYPTED_REPO_ID); + isOnlyChooseRepo = true; + mFoldersListView = (ListView) rootView.findViewById(R.id.cuc_multi_selection_lv); + mFoldersListView.setFastScrollEnabled(true); + mUpLayout = (RelativeLayout) rootView.findViewById(R.id.cuc_multi_selection_up_layout); + mCurrentFolderText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_current_folder_txt); + mEmptyText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_empty_msg); + mErrorText = (TextView) rootView.findViewById(R.id.cuc_multi_selection_error_msg); + mRefreshBtn = (ImageView) rootView.findViewById(R.id.cuc_multi_selection_refresh_iv); + mProgressContainer = rootView.findViewById(R.id.cuc_multi_selection_progress_container); + mListContainer = rootView.findViewById(R.id.cuc_multi_selection_list_container); + mDoneBtn = (Button) rootView.findViewById(R.id.cuc_remote_library_btn); + + mDoneBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mActivity.saveRepoConfig(); + mActivity.finish(); + } + }); + + mRefreshBtn.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + refreshList(true); + } + }); + + mUpLayout.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + try { + stepBack(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + }); + + mFoldersListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + onListItemClick(parent, position, id); + } + }); + + if (canChooseAccount) { + chooseAccount(); + } else { + chooseRepo(); + } + return rootView; + } + + @Override + public void onResume() { + super.onResume(); + loadAvatarUrls(48); + } + + private void refreshList(final boolean forceRefresh) { + switch (mStep) { + case STEP_CHOOSE_ACCOUNT: + if (mLoadAccountsTask != null && mLoadAccountsTask.getStatus() != AsyncTask.Status.FINISHED) { + return; + } else { + chooseAccount(false); + break; + } + case STEP_CHOOSE_REPO: + if (mLoadReposTask != null && mLoadReposTask.getStatus() != AsyncTask.Status.FINISHED) { + return; + } else { + chooseRepo(forceRefresh); + break; + } + case STEP_CHOOSE_DIR: + if (mLoadFolderTask != null && mLoadFolderTask.getStatus() != AsyncTask.Status.FINISHED) { + return; + } else { + SeafRepo repo = getDataManager().getCachedRepoByID(getNavContext().getRepoID()); + if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.id)) { + String password = getDataManager().getRepoPassword(repo.id); + showPasswordDialog(repo.name, repo.id, + new TaskDialog.TaskDialogListener() { + @Override + public void onTaskSuccess() { + chooseRepo(forceRefresh); + } + }, password); + } + chooseDir(forceRefresh); + break; + } + } + } + + public void onListItemClick(final View v, final int position, final long id) { + NavContext nav = getNavContext(); + SeafRepo repo = null; + if (mStep == STEP_CHOOSE_REPO) { + repo = getReposAdapter().getItem(position); + //mCurrentFolderText.setText(nav.getRepoName()); + } else if (mStep == STEP_CHOOSE_DIR) { + repo = getDataManager().getCachedRepoByID(nav.getRepoID()); + } + + if (repo != null) { + if (repo.encrypted && !getDataManager().getRepoPasswordSet(repo.id)) { + String password = getDataManager().getRepoPassword(repo.id); + showPasswordDialog(repo.name, repo.id, new TaskDialog.TaskDialogListener() { + @Override + public void onTaskSuccess() { + onListItemClick(v, position, id); + } + }, password); + + return; + } + } + + switch (mStep) { + case STEP_CHOOSE_ACCOUNT: + setAccount(getAccountAdapter().getItem(position)); + mCurrentDir = mAccount.getDisplayName(); + setCurrentDirText(mCurrentDir); + chooseRepo(); + break; + case STEP_CHOOSE_REPO: + if (!isOnlyChooseRepo) { + nav.setRepoName(repo.name); + nav.setRepoID(repo.id); + nav.setDir("/", repo.root); + chooseDir(); + } + mCurrentDir = getString(R.string.settings_cuc_remote_lib_repo, repo.name); + setCurrentDirText(mCurrentDir); + SeafRepo seafRepo = getReposAdapter().getItem(position); + onRepoSelected(mAccount, seafRepo); + break; + case STEP_CHOOSE_DIR: + SeafDirent dirent = getDirentsAdapter().getItem(position); + mCurrentDir += "/" + dirent.name; + setCurrentDirText(mCurrentDir); + + if (dirent.type == SeafDirent.DirentType.FILE) { + return; + } + + nav.setDir(Utils.pathJoin(nav.getDirPath(), dirent.name), dirent.id); + refreshFolder(); + break; + } + } + + private void onRepoSelected(Account account, SeafRepo seafRepo) { + mActivity.saveBackupLibrary(account, seafRepo); + getReposAdapter().setSelectedRepo(seafRepo); + getReposAdapter().notifyDataSetChanged(); + } + + private void stepBack() { + stepBack(false); + } + + private void stepBack(boolean cancelIfFirstStep) { + switch (mStep) { + case STEP_CHOOSE_ACCOUNT: + if (cancelIfFirstStep) { + mActivity.finish(); + } + mUpLayout.setVisibility(View.INVISIBLE); + break; + case STEP_CHOOSE_REPO: + if (canChooseAccount) { + mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); + setCurrentDirText(mCurrentDir); + chooseAccount(false); + } else if (cancelIfFirstStep) { + mActivity.finish(); + } + break; + case STEP_CHOOSE_DIR: + if (getNavContext().isRepoRoot()) { + mCurrentDir = getAccountManager().getCurrentAccount().getEmail(); + setCurrentDirText(mCurrentDir); + chooseRepo(); + } else { + String path = getNavContext().getDirPath(); + mCurrentDir = getNavContext().getRepoName() + Utils.getParentPath(path); + setCurrentDirText(mCurrentDir); + getNavContext().setDir(Utils.getParentPath(path), null); + refreshFolder(); + } + break; + } + } + + private void showPasswordDialog() { + NavContext nav = getNavContext(); + String repoName = nav.getRepoName(); + String repoID = nav.getRepoID(); + + showPasswordDialog(repoName, repoID, new TaskDialog.TaskDialogListener() { + @Override + public void onTaskSuccess() { + refreshFolder(); + } + }, null); + } + + public void showPasswordDialog(String repoName, String repoID, + TaskDialog.TaskDialogListener listener, String password) { + PasswordDialog passwordDialog = new PasswordDialog(); + passwordDialog.setRepo(repoName, repoID, mAccount); + if (password != null) { + passwordDialog.setPassword(password); + } + passwordDialog.setTaskDialogLisenter(listener); + passwordDialog.show(mActivity.getSupportFragmentManager(), PASSWORD_DIALOG_FRAGMENT_TAG); + } + + private void chooseDir() { + chooseDir(false); + } + + private void chooseDir(boolean forceRefresh) { + mStep = STEP_CHOOSE_DIR; + mUpLayout.setVisibility(View.VISIBLE); + mEmptyText.setText(R.string.dir_empty); + setListAdapter(getDirentsAdapter()); + refreshFolder(forceRefresh); + } + + private void chooseAccount() { + chooseAccount(true); + } + + /** + * List all accounts + */ + private void chooseAccount(boolean forwardIfOnlyOneAccount) { + mStep = STEP_CHOOSE_ACCOUNT; + mUpLayout.setVisibility(View.INVISIBLE); + mEmptyText.setText(R.string.no_account); + mCurrentDir = getString(R.string.settings_cuc_remote_lib_account); + setCurrentDirText(mCurrentDir); + mLoadAccountsTask = new LoadAccountsTask(getAccountManager(), forwardIfOnlyOneAccount); + ConcurrentAsyncTask.execute(mLoadAccountsTask); + setListAdapter(getAccountAdapter()); + } + + /** + * List all repos + */ + private void chooseRepo() { + chooseRepo(false); + } + + private void chooseRepo(boolean forceRefresh) { + mStep = STEP_CHOOSE_REPO; + mUpLayout.setVisibility(View.VISIBLE); + mCurrentDir = mAccount.getDisplayName(); + setCurrentDirText(mCurrentDir); + setListAdapter(getReposAdapter()); + getNavContext().setRepoID(null); + + if (!Utils.isNetworkOn() || !forceRefresh) { + List repos = getDataManager().getReposFromCache(); + if (repos != null) { + updateAdapterWithRepos(repos); + return; + } + } + mLoadReposTask = new LoadReposTask(getDataManager()); + ConcurrentAsyncTask.execute(mLoadReposTask); + } + + private void refreshFolder() { + refreshFolder(false); + } + + private void refreshFolder(boolean forceRefresh) { + String repoID = getNavContext().getRepoID(); + String dirPath = getNavContext().getDirPath(); + if (!Utils.isNetworkOn() || !forceRefresh) { + List dirents = getDataManager().getCachedDirents( + getNavContext().getRepoID(), getNavContext().getDirPath()); + if (dirents != null) { + updateAdapterWithDirents(dirents); + return; + } + } + mLoadFolderTask = new LoadFolderTask(repoID, dirPath, getDataManager()); + ConcurrentAsyncTask.execute(mLoadFolderTask); + } + + private void updateAdapterWithDirents(List dirents) { + getDirentsAdapter().setDirents(dirents); + showListOrEmptyText(dirents.size()); + } + + private void updateAdapterWithRepos(List repos) { + // remove encrypted repos in order to "hide" them in selection list + List filteredRepos = Lists.newArrayList(); + for (SeafRepo repo : repos) { + if (!repo.encrypted) + filteredRepos.add(repo); + } + getReposAdapter().setRepos(filteredRepos); + showListOrEmptyText(filteredRepos.size()); + } + + private CloudLibraryAccountAdapter getAccountAdapter() { + if (mAccountAdapter == null) { + mAccountAdapter = new CloudLibraryAccountAdapter(mActivity); + } + return mAccountAdapter; + } + + private CloudLibraryAdapter getReposAdapter() { + if (mReposAdapter == null) { + mReposAdapter = new CloudLibraryAdapter(onlyShowWritableRepos, encryptedRepoId); + } + return mReposAdapter; + } + + private DirentsAdapter getDirentsAdapter() { + if (mDirentsAdapter == null) { + mDirentsAdapter = new DirentsAdapter(); + } + return mDirentsAdapter; + } + + private void showListOrEmptyText(int listSize) { + if (listSize == 0) { + mFoldersListView.setVisibility(View.GONE); + mEmptyText.setVisibility(View.VISIBLE); + } else { + mFoldersListView.setVisibility(View.VISIBLE); + mEmptyText.setVisibility(View.GONE); + } + } + + private void setListAdapter(BaseAdapter adapter) { + mFoldersListView.setAdapter(adapter); + } + + private DataManager getDataManager() { + if (mDataManager == null) { + mDataManager = new DataManager(mAccount); + } + return mDataManager; + } + + private void setAccount(Account account) { + mAccount = account; + mDataManager = new DataManager(account); + } + + private AccountManager getAccountManager() { + if (mAccountManager == null) { + mAccountManager = new AccountManager(getActivity()); + } + return mAccountManager; + } + + private NavContext getNavContext() { + if (mNavContext == null) { + mNavContext = new NavContext(); + } + return mNavContext; + } + + /** + * Sets the current directory's text. + */ + private void setCurrentDirText(String text) { + mCurrentFolderText.setText(text); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getActivity().finish(); + } + + @Override + public void onPause() { + super.onPause(); + getActivity().finish(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (isRemoving()) { + mCursor.close(); + mCursor = null; + } + } + + private class LoadAccountsTask extends AsyncTask { + private List accounts; + private Exception err; + private AccountManager accountManager; + private boolean forwardIfOnlyOneAccount; + + public LoadAccountsTask(AccountManager accountManager, boolean forwardIfOnlyOneAccount) { + this.accountManager = accountManager; + this.forwardIfOnlyOneAccount = forwardIfOnlyOneAccount; + } + + @Override + protected void onPreExecute() { + showLoading(true); + } + + @Override + protected Void doInBackground(Void... params) { + try { + accounts = accountManager.getAccountList(); + } catch (Exception e) { + err = e; + } + return null; + } + + @Override + protected void onPostExecute(Void v) { + showLoading(false); + if (err != null || accounts == null) { + setErrorMessage(R.string.load_accounts_fail); + if (err != null) { + Log.d(DEBUG_TAG, "failed to load accounts: " + err.getMessage()); + } + return; + } + + if (accounts.size() == 1 && forwardIfOnlyOneAccount) { + // Only 1 account. Go to next step. + setAccount(accounts.get(0)); + chooseRepo(); + return; + } + + CloudLibraryAccountAdapter adapter = getAccountAdapter(); + adapter.clear(); + for (Account account : accounts) { + adapter.add(account); + } + adapter.notifyDataSetChanged(); + showListOrEmptyText(accounts.size()); + } + } + + private class LoadReposTask extends AsyncTask { + private List repos; + private SeafException err; + private DataManager dataManager; + + public LoadReposTask(DataManager dataManager) { + this.dataManager = dataManager; + } + + @Override + protected Void doInBackground(Void... params) { + try { + repos = dataManager.getReposFromServer(); + } catch (SeafException e) { + err = e; + } + return null; + } + + @Override + protected void onPreExecute() { + showLoading(true); + } + + @Override + protected void onPostExecute(Void v) { + if (mStep != STEP_CHOOSE_REPO) { + return; + } + showLoading(false); + if (err != null || repos == null) { + setErrorMessage(R.string.load_libraries_fail); + Log.d(DEBUG_TAG, "failed to load repos: " + (err != null ? err.getMessage() : " no error present")); + return; + } + updateAdapterWithRepos(repos); + } + } + + private class LoadFolderTask extends AsyncTask { + private String repoID, dirPath; + private SeafException err; + private DataManager dataManager; + private List dirents; + + public LoadFolderTask(String repoID, String dirPath, DataManager dataManager) { + this.repoID = repoID; + this.dirPath = dirPath; + this.dataManager = dataManager; + } + + @Override + protected void onPreExecute() { + showLoading(true); + } + + @Override + protected Void doInBackground(Void... params) { + try { + dirents = dataManager.getDirentsFromServer(repoID, dirPath); + } catch (SeafException e) { + err = e; + } + return null; + } + + @Override + protected void onPostExecute(Void v) { + if (mStep != STEP_CHOOSE_DIR) { + return; + } + getDirentsAdapter().clearDirents(); + showLoading(false); + if (err != null) { + int retCode = err.getCode(); + if (retCode == SeafConnection.HTTP_STATUS_REPO_PASSWORD_REQUIRED) { + showPasswordDialog(); + } else if (retCode == HttpURLConnection.HTTP_NOT_FOUND) { + final String message = String.format(getString(R.string.op_exception_folder_deleted), dirPath); + mActivity.showShortToast(mActivity, message); + } else { + Log.d(DEBUG_TAG, "failed to load dirents: " + err.getMessage()); + err.printStackTrace(); + setErrorMessage(R.string.load_dir_fail); + } + return; + } + + if (dirents == null) { + Log.d(DEBUG_TAG, "failed to load dirents: no error present"); + setErrorMessage(R.string.load_dir_fail); + return; + } + updateAdapterWithDirents(dirents); + } + } + + public void loadAvatarUrls(int avatarSize) { + List avatars; + if (!Utils.isNetworkOn() || !avatarManager.isNeedToLoadNewAvatars()) { + avatars = avatarManager.getAvatarList(); + if (avatars == null) { + return; + } + mAccountAdapter.setAvatars((ArrayList) avatars); + mAccountAdapter.notifyDataSetChanged(); + return; + } + LoadAvatarUrlsTask task = new LoadAvatarUrlsTask(avatarSize); + ConcurrentAsyncTask.execute(task); + } + + private class LoadAvatarUrlsTask extends AsyncTask> { + + private List avatars; + private int avatarSize; + private SeafConnection httpConnection; + + public LoadAvatarUrlsTask(int avatarSize) { + this.avatarSize = avatarSize; + this.avatars = Lists.newArrayList(); + } + + @Override + protected List doInBackground(Void... params) { + avatars = avatarManager.getAvatarList(); + List acts = avatarManager.getAccountsWithoutAvatars(); + List newAvatars = new ArrayList(acts.size()); + for (Account account : acts) { + httpConnection = new SeafConnection(account); + String avatarRawData = null; + try { + avatarRawData = httpConnection.getAvatar(account.getEmail(), avatarSize); + } catch (SeafException e) { + e.printStackTrace(); + return avatars; + } + Avatar avatar = avatarManager.parseAvatar(avatarRawData); + if (avatar == null) + continue; + + avatar.setSignature(account.getSignature()); + avatars.add(avatar); + newAvatars.add(avatar); + } + avatarManager.saveAvatarList(newAvatars); + return avatars; + } + + @Override + protected void onPostExecute(List avatars) { + if (avatars == null) { + return; + } + mAccountAdapter.setAvatars((ArrayList) avatars); + mAccountAdapter.notifyDataSetChanged(); + } + } + + private void setErrorMessage(int resID) { + mErrorText.setVisibility(View.VISIBLE); + mErrorText.setText(getString(resID)); + } + + private void clearError() { + mErrorText.setVisibility(View.GONE); + } + + private void showLoading(boolean loading) { + clearError(); + if (loading) { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + mProgressContainer.setVisibility(View.VISIBLE); + mListContainer.setVisibility(View.INVISIBLE); + } else { + mProgressContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out)); + mListContainer.startAnimation(AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in)); + mProgressContainer.setVisibility(View.GONE); + mListContainer.setVisibility(View.VISIBLE); + } + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java new file mode 100644 index 000000000..909e65249 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupConfigActivity.java @@ -0,0 +1,229 @@ +package com.seafile.seadroid2.folderbackup; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Bundle; +import android.os.IBinder; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentStatePagerAdapter; +import android.support.v4.view.ViewPager; +import android.text.TextUtils; +import android.widget.Toast; +import com.google.gson.Gson; +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.data.SeafRepo; +import com.seafile.seadroid2.folderbackup.selectfolder.SelectBackupFolderFragment; +import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; +import com.seafile.seadroid2.ui.activity.BaseActivity; +import com.seafile.seadroid2.ui.activity.SeafilePathChooserActivity; +import com.seafile.seadroid2.ui.fragment.SettingsFragment; +import com.seafile.seadroid2.util.Utils; +import java.util.ArrayList; +import java.util.List; + +public class FolderBackupConfigActivity extends BaseActivity { + + public String DEBUG_TAG = "FolderBackupConfigActivity"; + public static final String BACKUP_SELECT_REPO = "backup_select_repo"; + public static final String BACKUP_SELECT_PATHS = "backup_select_paths"; + public static final String BACKUP_SELECT_PATHS_SWITCH = "backup_select_paths_switch"; + private ViewPager mViewPager; + private SelectBackupFolderFragment mBucketsFragment; + private CloudLibraryChooserFragment mCloudLibFragment; + private SeafRepo mSeafRepo; + private Account mAccount; + private boolean isChooseFolderPage; + private boolean isChooseLibPage; + private FolderBackupDBHelper databaseHelper; + private FolderBackupService mBackupService; + private List selectFolderPaths; + private Activity mActivity; + private String originalBackupPaths; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + setContentView(R.layout.folder_backup_activity_layout); + if (getSupportActionBar() != null) + getSupportActionBar().hide(); + + isChooseFolderPage = getIntent().getBooleanExtra(SettingsFragment.FOLDER_BACKUP_REMOTE_PATH, false); + isChooseLibPage = getIntent().getBooleanExtra(SettingsFragment.FOLDER_BACKUP_REMOTE_LIBRARY, false); + mViewPager = (ViewPager) findViewById(R.id.cuc_pager); + FragmentManager fm = getSupportFragmentManager(); + mViewPager.setAdapter(new FolderBackupConfigAdapter(fm)); + mViewPager.setOffscreenPageLimit(2); + databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); + Intent bindIntent = new Intent(this, FolderBackupService.class); + bindService(bindIntent, mBackupConnection, Context.BIND_AUTO_CREATE); + mActivity = this; + originalBackupPaths = SettingsManager.instance().getBackupPaths(); + + if (isChooseFolderPage && !TextUtils.isEmpty(originalBackupPaths)) { + selectFolderPaths = StringTools.getJsonToList(originalBackupPaths); + } + } + + private ServiceConnection mBackupConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder binder) { + FolderBackupService.FileBackupBinder fileBackupBinder = (FolderBackupService.FileBackupBinder) binder; + mBackupService = fileBackupBinder.getService(); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + mBackupService = null; + } + + }; + + public void saveBackupLibrary(Account account, SeafRepo seafRepo) { + mSeafRepo = seafRepo; + mAccount = account; + } + + public FolderBackupDBHelper getDatabaseHelper() { + return databaseHelper; + } + + public void setFolderPathList(List selectFileList) { + this.selectFolderPaths = selectFileList; + } + + public List getSelectFolderPath() { + return selectFolderPaths; + } + + public void saveRepoConfig() { + if (isChooseLibPage) { + Intent intent = new Intent(); + // update cloud library data + if (mSeafRepo != null && mAccount != null) { + intent.putExtra(SeafilePathChooserActivity.DATA_REPO_NAME, mSeafRepo.name); + intent.putExtra(SeafilePathChooserActivity.DATA_REPO_ID, mSeafRepo.id); + intent.putExtra(SeafilePathChooserActivity.DATA_ACCOUNT, mAccount); + intent.putExtra(BACKUP_SELECT_REPO, true); + SettingsManager.instance().saveBackupEmail(mAccount.getEmail()); + try { + RepoConfig repoConfig = databaseHelper.getRepoConfig(mAccount.getEmail()); + if (repoConfig != null) { + databaseHelper.updateRepoConfig(mAccount.getEmail(), mSeafRepo.getID(), mSeafRepo.getName()); + } else { + databaseHelper.saveRepoConfig(mAccount.getEmail(), mSeafRepo.getID(), mSeafRepo.getName()); + } + Toast.makeText(mActivity, mActivity.getString(R.string.folder_backup_select_repo_update), Toast.LENGTH_SHORT).show(); + } catch (Exception e) { + Utils.utilsLogInfo(true, "=saveRepoConfig=======================" + e.toString()); + } + + } + setResult(RESULT_OK, intent); + boolean automaticBackup = SettingsManager.instance().isFolderAutomaticBackup(); + if (automaticBackup && mBackupService != null) { + mBackupService.folderBackup(mAccount.getEmail()); + } + } + } + + public void saveFolderConfig() { + if (isChooseFolderPage) { + String backupEmail = SettingsManager.instance().getBackupEmail(); + String strJsonPath = new Gson().toJson(selectFolderPaths); + + if ((TextUtils.isEmpty(originalBackupPaths) && !TextUtils.isEmpty(strJsonPath)) || + !originalBackupPaths.equals(strJsonPath)) { + mBackupService.startFolderMonitor(selectFolderPaths); + Utils.utilsLogInfo(false, "----------Restart monitoring FolderMonitor"); + } + if (!TextUtils.isEmpty(originalBackupPaths) && TextUtils.isEmpty(strJsonPath)) { + mBackupService.stopFolderMonitor(); + } + SettingsManager.instance().saveBackupPaths(strJsonPath); + Intent intent = new Intent(); + if (selectFolderPaths != null) { + intent.putStringArrayListExtra(BACKUP_SELECT_PATHS, (ArrayList) selectFolderPaths); + intent.putExtra(BACKUP_SELECT_PATHS_SWITCH, true); + } + setResult(RESULT_OK, intent); + boolean folderAutomaticBackup = SettingsManager.instance().isFolderAutomaticBackup(); + if (folderAutomaticBackup && mBackupService != null) { + mBackupService.folderBackup(backupEmail); + } + } + + } + + @Override + public void onBackPressed() { + if (mBucketsFragment != null && mBucketsFragment.onBackPressed()) { + return; + } + setResult(RESULT_CANCELED); + super.onBackPressed(); + } + + public boolean isChooseDirPage() { + return isChooseFolderPage; + } + + class FolderBackupConfigAdapter extends FragmentStatePagerAdapter { + + public FolderBackupConfigAdapter(FragmentManager fm) { + super(fm); + } + + @Override + public Fragment getItem(int position) { + + if (isChooseLibPage) { + return position == 0 ? new CloudLibraryChooserFragment() : null; + } + if (isChooseFolderPage) { + switch (position) { + case 0: + mBucketsFragment = new SelectBackupFolderFragment(); + return mBucketsFragment; + default: + return null; + } + + } + switch (position) { + case 0: + mCloudLibFragment = new CloudLibraryChooserFragment(); + return mCloudLibFragment; + case 1: + mBucketsFragment = new SelectBackupFolderFragment(); + return mBucketsFragment; + default: + return null; + } + } + + @Override + public int getCount() { + if (isChooseLibPage || isChooseFolderPage) + return 1; + else + return 2; + } + } + + @Override + protected void onDestroy() { + if (mBackupService != null) { + unbindService(mBackupConnection); + mBackupService = null; + } + super.onDestroy(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java new file mode 100644 index 000000000..7d5e1c2d6 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupDBHelper.java @@ -0,0 +1,202 @@ +package com.seafile.seadroid2.folderbackup; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; + +import com.seafile.seadroid2.SeadroidApplication; + +import java.io.File; + +public class FolderBackupDBHelper extends SQLiteOpenHelper { + + private SQLiteDatabase database = null; + private static FolderBackupDBHelper dbHelper = null; + // version. + public static final int DATABASE_VERSION = 1; + public static final String DATABASE_NAME = "folder_backup.db"; + // backup file table + private static final String SAVE_FOLDER_BACKUP_FILE_TABLE_NAME = "FolderBackupInfo"; + private static final String FOLDER_BACKUP_COLUMN_ID = "id"; + private static final String FOLDER_BACKUP_COLUMN_REPO_ID = "repo_id"; + private static final String FOLDER_BACKUP_COLUMN_REPO_NAME = "repo_name"; + private static final String FOLDER_BACKUP_COLUMN_PARENT_FOLDER = "parent_folder"; + private static final String FOLDER_BACKUP_COLUMN_FILE_NAME = "file_name"; + private static final String FOLDER_BACKUP_COLUMN_FILE_PATH = "file_path"; + private static final String FOLDER_BACKUP_COLUMN_FILE_SIZE = "file_size"; + // repo config table + private static final String REPO_CONFIG_TABLE_NAME = "RepoConfig"; + private static final String REPO_CONFIG_COLUMN_ID = "id"; + private static final String REPO_CONFIG_MAIL = "mail"; + private static final String REPO_CONFIG_REPO_ID = "repo_id"; + private static final String REPO_CONFIG_REPO_NAME = "repo_name"; + + private static final String SQL_CREATE_REPO_CONFIG_TABLE = + "CREATE TABLE " + REPO_CONFIG_TABLE_NAME + " (" + + REPO_CONFIG_COLUMN_ID + " INTEGER PRIMARY KEY, " + + REPO_CONFIG_MAIL + " TEXT NOT NULL, " + + REPO_CONFIG_REPO_ID + " TEXT NOT NULL, " + + REPO_CONFIG_REPO_NAME + " TEXT NOT NULL);"; + + private static final String SQL_CREATE_AUTO_BACKUP_FOLDER_TABLE = "CREATE TABLE " + + SAVE_FOLDER_BACKUP_FILE_TABLE_NAME + + " (" + + FOLDER_BACKUP_COLUMN_ID + + " INTEGER PRIMARY KEY, " + + FOLDER_BACKUP_COLUMN_REPO_ID + + " TEXT NOT NULL, " + + FOLDER_BACKUP_COLUMN_REPO_NAME + + " TEXT NOT NULL, " + + FOLDER_BACKUP_COLUMN_PARENT_FOLDER + + " TEXT NOT NULL, " + + FOLDER_BACKUP_COLUMN_FILE_NAME + + " TEXT NOT NULL, " + + FOLDER_BACKUP_COLUMN_FILE_PATH + + " TEXT NOT NULL, " + + FOLDER_BACKUP_COLUMN_FILE_SIZE + + " TEXT NOT NULL);"; + + public static synchronized FolderBackupDBHelper getDatabaseHelper() { + if (dbHelper != null) + return dbHelper; + dbHelper = new FolderBackupDBHelper(SeadroidApplication.getAppContext()); + dbHelper.database = dbHelper.getWritableDatabase(); + return dbHelper; + } + + private FolderBackupDBHelper(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + createFolderBackupTable(db); + } + + private void createFolderBackupTable(SQLiteDatabase db) { + db.execSQL(SQL_CREATE_AUTO_BACKUP_FOLDER_TABLE); + db.execSQL(SQL_CREATE_REPO_CONFIG_TABLE); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + db.execSQL("DROP TABLE IF EXISTS " + SAVE_FOLDER_BACKUP_FILE_TABLE_NAME + ";"); + db.execSQL("DROP TABLE IF EXISTS " + SQL_CREATE_REPO_CONFIG_TABLE + ";"); + onCreate(db); + } + + @Override + public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { + onUpgrade(db, oldVersion, newVersion); + } + + public void saveFileBackupInfo(FolderBackupInfo info) { + ContentValues values = new ContentValues(); + values.put(FOLDER_BACKUP_COLUMN_REPO_ID, info.repoID); + values.put(FOLDER_BACKUP_COLUMN_REPO_NAME, info.repoName); + values.put(FOLDER_BACKUP_COLUMN_PARENT_FOLDER, info.parentFolder); + values.put(FOLDER_BACKUP_COLUMN_FILE_NAME, info.fileName); + values.put(FOLDER_BACKUP_COLUMN_FILE_PATH, info.filePath); + values.put(FOLDER_BACKUP_COLUMN_FILE_SIZE, info.fileSize); + database.insert(SAVE_FOLDER_BACKUP_FILE_TABLE_NAME, null, values); + } + + public void saveRepoConfig(String email, String repoID, String repoName) { + ContentValues values = new ContentValues(); + values.put(REPO_CONFIG_MAIL, email); + values.put(REPO_CONFIG_REPO_ID, repoID); + values.put(REPO_CONFIG_REPO_NAME, repoName); + database.insert(REPO_CONFIG_TABLE_NAME, null, values); + } + + public void updateRepoConfig(String email, String repoID, String repoName) { + removeRepoConfig(email); + saveRepoConfig(email, repoID, repoName); + } + + public RepoConfig getRepoConfig(String email) { + String[] projection = { + REPO_CONFIG_REPO_ID, + REPO_CONFIG_REPO_NAME + }; + String selectClause = String.format("%s = ? ", + REPO_CONFIG_MAIL); + String[] selectArgs = {email}; + Cursor c = database.query( + REPO_CONFIG_TABLE_NAME, + projection, + selectClause, + selectArgs, + null, + null, + null); + if (!c.moveToFirst()) { + c.close(); + return null; + } + RepoConfig item = cursorToRepoConfigInfo(c, email); + c.close(); + return item; + } + + public FolderBackupInfo getBackupFileInfo(String repoID, String filePath, String fileSize) { + String[] projection = { + FOLDER_BACKUP_COLUMN_REPO_ID, + FOLDER_BACKUP_COLUMN_REPO_NAME, + FOLDER_BACKUP_COLUMN_PARENT_FOLDER, + FOLDER_BACKUP_COLUMN_FILE_NAME, + FOLDER_BACKUP_COLUMN_FILE_PATH, + FOLDER_BACKUP_COLUMN_FILE_SIZE + }; + String selectClause = String.format("%s = ? and %s = ? and %s = ?", + FOLDER_BACKUP_COLUMN_REPO_ID, + FOLDER_BACKUP_COLUMN_FILE_PATH, + FOLDER_BACKUP_COLUMN_FILE_SIZE); + String[] selectArgs = {repoID, filePath, fileSize}; + Cursor c = database.query( + SAVE_FOLDER_BACKUP_FILE_TABLE_NAME, + projection, + selectClause, + selectArgs, + null, + null, + null); + if (!c.moveToFirst()) { + c.close(); + return null; + } + FolderBackupInfo item = cursorToBackupUpdateInfo(c); + c.close(); + return item; + } + + public void removeRepoConfig(String email) { + String whereClause = String.format("%s = ?", REPO_CONFIG_MAIL); + String[] params = {email}; + database.delete(REPO_CONFIG_TABLE_NAME, whereClause, params); + } + + private FolderBackupInfo cursorToBackupUpdateInfo(Cursor c) { + String repoID = c.getString(0); + String repoName = c.getString(1); + String parentDir = c.getString(2); + String fileName = c.getString(3); + String filePath = c.getString(4); + String fileSize = c.getString(5); + if (!new File(filePath).exists()) { + filePath = null; + } + FolderBackupInfo info = new FolderBackupInfo(repoID, repoName, + parentDir, fileName, filePath, fileSize); + return info; + } + + private RepoConfig cursorToRepoConfigInfo(Cursor c, String email) { + String repoID = c.getString(0); + String repoName = c.getString(1); + RepoConfig info = new RepoConfig(repoID, repoName, email); + return info; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java new file mode 100644 index 000000000..aff13b103 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupEvent.java @@ -0,0 +1,13 @@ +package com.seafile.seadroid2.folderbackup; + +public class FolderBackupEvent { + private String backupInfo; + + public FolderBackupEvent(String backupInfo) { + this.backupInfo = backupInfo; + } + + public String getBackupInfo() { + return backupInfo; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java new file mode 100644 index 000000000..e71798d5f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupInfo.java @@ -0,0 +1,51 @@ +package com.seafile.seadroid2.folderbackup; + +import com.google.common.base.Objects; + +import java.io.Serializable; + +public class FolderBackupInfo implements Serializable { + private volatile int hashCode = 0; + public String repoID; + public String repoName; + public String parentFolder; + public String filePath; + public String fileName; + public String fileSize; + + public FolderBackupInfo(String repoID, String repoName, String parentPath, + String fileName, String filePath, String fileSize) { + this.repoID = repoID; + this.repoName = repoName; + this.parentFolder = parentPath; + this.fileName = fileName; + this.filePath = filePath; + this.fileSize = fileSize; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || (obj.getClass() != this.getClass())) + return false; + + FolderBackupInfo that = (FolderBackupInfo) obj; + if (that.repoID == null || that.repoName == null || + that.parentFolder == null || that.filePath == null) { + return false; + } + return that.repoID.equals(this.repoID) && + that.repoName.equals(this.repoName) && + that.parentFolder.equals(this.parentFolder) && + that.filePath.equals(this.filePath); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Objects.hashCode(repoID, repoName, parentFolder, filePath); + } + return hashCode; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java new file mode 100644 index 000000000..d48f8e74d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/FolderBackupService.java @@ -0,0 +1,325 @@ +package com.seafile.seadroid2.folderbackup; + +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.Log; + +import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.account.Account; +import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.data.DataManager; +import com.seafile.seadroid2.folderbackup.selectfolder.FileBean; +import com.seafile.seadroid2.folderbackup.selectfolder.FileTools; +import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; +import com.seafile.seadroid2.transfer.TransferManager; +import com.seafile.seadroid2.transfer.TransferService; +import com.seafile.seadroid2.transfer.UploadTaskManager; +import com.seafile.seadroid2.util.CameraSyncStatus; +import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.Utils; + +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; +import org.greenrobot.eventbus.EventBus; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +public class FolderBackupService extends Service { + private static final String DEBUG_TAG = "FolderBackupService"; + private Map fileUploaded = new HashMap<>(); + private final IBinder mBinder = new FileBackupBinder(); + private TransferService txService = null; + private DataManager dataManager; + private FolderBackupDBHelper databaseHelper; + private AccountManager accountManager; + private Account currentAccount; + private RepoConfig repoConfig; + private List backupPathsList; + private FolderReceiver mFolderReceiver; + private FileAlterationMonitor fileMonitor; + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + public class FileBackupBinder extends Binder { + public FolderBackupService getService() { + return FolderBackupService.this; + } + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_STICKY; + } + + @Override + public void onCreate() { + super.onCreate(); + + databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); + Intent bIntent = new Intent(this, TransferService.class); + accountManager = new AccountManager(this); + bindService(bIntent, mConnection, Context.BIND_AUTO_CREATE); + if (mFolderReceiver == null) { + mFolderReceiver = new FolderReceiver(); + } + IntentFilter filter = new IntentFilter(TransferManager.BROADCAST_ACTION); + LocalBroadcastManager.getInstance(this).registerReceiver(mFolderReceiver, filter); + String backupPaths = SettingsManager.instance().getBackupPaths(); + if (!TextUtils.isEmpty(backupPaths)) { + List pathsList = StringTools.getJsonToList(backupPaths); + if (pathsList != null) { + startFolderMonitor(pathsList); + } + } + } + + public void startFolderMonitor(List backupPaths) { + List fileAlterationObserverList = new ArrayList<>(); + FolderMonitor folderFileMonitor; + FileAlterationObserver folderFileObserver; + for (String str : backupPaths) { + folderFileMonitor = new FolderMonitor(); + folderFileObserver = new FileAlterationObserver(str); + folderFileObserver.addListener(folderFileMonitor); + fileAlterationObserverList.add(folderFileObserver); + } + fileMonitor = new FileAlterationMonitor(1000l, fileAlterationObserverList); + try { + fileMonitor.start(); + } catch (Exception e) { + Log.w(DEBUG_TAG, "failed to start file monitor"); + throw new RuntimeException("failed to start file monitor"); + } + } + + public void stopFolderMonitor() { + if (fileMonitor != null) { + try { + fileMonitor.stop(); + } catch (Exception e) { + Log.w(DEBUG_TAG, "failed to stop file monitor"); + throw new RuntimeException("failed to stop file monitor"); + } + } + } + + public void folderBackup(String email) { + fileUploaded.clear(); + if (databaseHelper == null) { + databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); + } + if (!TextUtils.isEmpty(email)) { + try { + repoConfig = databaseHelper.getRepoConfig(email); + } catch (Exception e) { + repoConfig = null; + } + } + String backupPaths = SettingsManager.instance().getBackupPaths(); + if (repoConfig == null || TextUtils.isEmpty(backupPaths)) { + return; + } + if (accountManager == null) { + accountManager = new AccountManager(this); + } + currentAccount = accountManager.getCurrentAccount(); + backupPathsList = StringTools.getJsonToList(backupPaths); + dataManager = new DataManager(currentAccount); + if (!StringTools.checkFolderUploadNetworkAvailable()) { + SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.NETWORK_UNAVAILABLE); + return; + } + ConcurrentAsyncTask.execute(new backupFolderTask()); + + } + + class backupFolderTask extends AsyncTask { + + @Override + protected String doInBackground(Void... params) { + for (String str : backupPathsList) { + String[] split = str.split("/"); + isFolder(split[split.length - 1] + "/", str); + } + return null; + } + + @Override + protected void onPostExecute(String FilePath) { + Utils.utilsLogInfo(false, "----------" + FilePath); + } + } + + private void isFolder(String parentPath, String filePath) { + List fileBeanList = new ArrayList<>(); + FileBean fileBean; + File file = FileTools.getFileByPath(filePath); + File[] files = file.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + fileBean = new FileBean(files[i].getAbsolutePath()); + fileBeanList.add(fileBean); + } + } + if (fileBeanList == null || fileBeanList.size() == 0) return; + for (FileBean fb : fileBeanList) { + if (fb.isDir()) { + isFolder(parentPath + fb.getFileName() + "/", fb.getFilePath()); + } else { + Utils.utilsLogInfo(false, "=relative_path==============" + parentPath + "--------" + fb.getFilePath()); + + FolderBackupInfo fileInfo = databaseHelper.getBackupFileInfo(repoConfig.getRepoID(), + fb.getFilePath(), fb.getSimpleSize() + ""); + if (fileInfo != null && !TextUtils.isEmpty(fileInfo.filePath)) { + Utils.utilsLogInfo(false, "===============" + fileInfo.filePath); + } else { + + int taskID = txService.addTaskToSourceQue(Utils.TRANSFER_FOLDER_TAG, currentAccount, repoConfig.getRepoID(), + repoConfig.getRepoName(), parentPath, fb.getFilePath(), false, true); + if (taskID != 0) { + FolderBackupInfo dirInfo = new FolderBackupInfo(repoConfig.getRepoID(), repoConfig.getRepoName(), + parentPath, fb.getFileName(), fb.getFilePath(), fb.getSimpleSize() + ""); + fileUploaded.put(taskID + "", dirInfo); + } + } + + } + } + + } + + ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + TransferService.TransferBinder binder = (TransferService.TransferBinder) service; + synchronized (this) { + txService = binder.getService(); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + synchronized (this) { + txService = null; + } + } + }; + + + @Override + public void onDestroy() { + if (txService != null) { + unbindService(mConnection); + txService = null; + } + if (mFolderReceiver != null) { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mFolderReceiver); + } + stopFolderMonitor(); + super.onDestroy(); + } + + class FolderMonitor implements FileAlterationListener { + + public FolderMonitor() { + + } + + @Override + public void onStart(FileAlterationObserver observer) { + + } + + @Override + public void onDirectoryCreate(File directory) { + backupFolders(); + } + + @Override + public void onDirectoryChange(File directory) { + + } + + @Override + public void onDirectoryDelete(File directory) { + + } + + @Override + public void onFileCreate(File file) { + backupFolders(); + } + + @Override + public void onFileChange(File file) { + + } + + @Override + public void onFileDelete(File file) { + + } + + @Override + public void onStop(FileAlterationObserver observer) { + + } + } + + public void backupFolders() { + String backupEmail = SettingsManager.instance().getBackupEmail(); + if (backupEmail != null) { + folderBackup(backupEmail); + } + } + + private class FolderReceiver extends BroadcastReceiver { + + private FolderReceiver() { + } + + public void onReceive(Context context, Intent intent) { + String type = intent.getStringExtra("type"); + if (type.equals(UploadTaskManager.BROADCAST_FILE_UPLOAD_SUCCESS)) { + int taskID = intent.getIntExtra("taskID", 0); + onFileBackedUp(taskID); + } + } + + } + + private void onFileBackedUp(int taskID) { + if (fileUploaded != null) { + FolderBackupInfo uploadInfo = fileUploaded.get(taskID + ""); + if (databaseHelper == null) { + databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); + } + if (uploadInfo != null) { + databaseHelper.saveFileBackupInfo(uploadInfo); + } + EventBus.getDefault().post(new FolderBackupEvent("folderBackup")); + } + + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java new file mode 100644 index 000000000..c642fe461 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/RepoConfig.java @@ -0,0 +1,72 @@ +package com.seafile.seadroid2.folderbackup; + +import com.google.common.base.Objects; +import com.seafile.seadroid2.SettingsManager; + +import java.io.Serializable; + +public class RepoConfig implements Serializable { + private String email; + private String repoID; + private String repoName; + + public RepoConfig(String repoID, String repoName, String email) { + this.repoID = repoID; + this.repoName = repoName; + this.email = email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getEmail() { + return email; + } + + public void setRepoID(String repoID) { + this.repoID = repoID; + } + + public String getRepoID() { + return repoID; + } + + public void setRepoName(String repoName) { + this.repoName = repoName; + } + + public String getRepoName() { + return repoName; + } + + public boolean canLocalDecrypt() { + return SettingsManager.instance().isEncryptEnabled(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || (obj.getClass() != this.getClass())) + return false; + + RepoConfig that = (RepoConfig) obj; + if (that.repoID == null || that.repoName == null || that.email == null) { + return false; + } + + return that.email.equals(this.email) && that.repoID.equals(this.repoID) && + that.repoName.equals(this.repoName); + } + + private volatile int hashCode = 0; + + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Objects.hashCode(email, repoID, repoName); + } + return hashCode; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java new file mode 100644 index 000000000..3b3db394e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/BeanListManager.java @@ -0,0 +1,184 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.app.Activity; +import android.app.ProgressDialog; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeadroidApplication; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.annotations.NonNull; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.core.Observer; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.functions.Function; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public class BeanListManager { + public static final int TypeAddTabbar = 0; + public static final int TypeDelTabbar = 1; + public static final int TypeInitTabbar = 2; + + public static void upDataFileBeanListByAsyn(Activity at, List selectFilePath, + List fileBeanList, FileListAdapter fileListAdapter, + String path, List fileTypes, int sortType) { + + if (fileBeanList == null) { + fileBeanList = new ArrayList<>(); + } else if (fileBeanList.size() != 0) { + fileBeanList.clear(); + } + + Observable.just(fileBeanList).map(new Function, List>() { + @Override + public List apply(List fileBeanList) throws Throwable { + FileBean fileBean; + File file = FileTools.getFileByPath(path); + File[] files = file.listFiles(); + if (files != null) { + for (int i = 0; i < files.length; i++) { + fileBean = new FileBean(files[i].getAbsolutePath()); + if (selectFilePath != null && selectFilePath.size() > 0) { + for (String str : selectFilePath) { + if (fileBean.getFilePath().equals(str)) { + fileBean.setChecked(true); + } + } + } + fileBeanList.add(fileBean); + } + } + sortFileBeanList(fileBeanList, sortType); + return fileBeanList; + } + }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Observer>() { + public ProgressDialog pg; + + @Override + public void onSubscribe(@NonNull Disposable d) { + pg = new ProgressDialog(at); + pg.show(); + } + + @Override + public void onNext(@NonNull List fileBeans) { + if (fileListAdapter != null) { + fileListAdapter.updateListData(fileBeans); + fileListAdapter.notifyDataSetChanged(); + } + } + + @Override + public void onError(@NonNull Throwable e) { + pg.dismiss(); + } + + @Override + public void onComplete() { + pg.dismiss(); + } + }); + } + + public static void sortFileBeanList(List fileBeanList, int sortType) { + Collections.sort(fileBeanList, new Comparator() { + @Override + public int compare(FileBean file1, FileBean file2) { + + if (file1.isDir() && file2.isFile()) + return -1; + if (file1.isFile() && file2.isDir()) + return 1; + + switch (sortType) { + case Constants.SORT_NAME_ASC: + return file1.getFileName().compareToIgnoreCase(file2.getFileName()); + case Constants.SORT_NAME_DESC: + return file2.getFileName().compareToIgnoreCase(file1.getFileName()); + case Constants.SORT_TIME_ASC: + long diff = file1.getModifyTime() - file2.getModifyTime(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_TIME_DESC: + diff = file2.getModifyTime() - file1.getModifyTime(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_SIZE_ASC: + diff = file1.getSimpleSize() - file2.getSimpleSize(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + case Constants.SORT_SIZE_DESC: + diff = file2.getSimpleSize() - file1.getSimpleSize(); + if (diff > 0) + return 1; + else if (diff == 0) + return 0; + else + return -1; + default: + return 0; + } + } + }); + } + + public static void getTabbarFileBeanList(List tabbarList, + String path, List allPathsList) { + if (allPathsList.contains(path)) { + tabbarList.add(0, new TabbarFileBean(path, + SeadroidApplication.getAppContext().getString(R.string.internal_storage))); + return; + } + } + + public static List upDataTabbarFileBeanList(List tabbarList, + TabbarFileListAdapter tabbarAdapter, + String path, int type, List allPathsList) { + switch (type) { + case TypeAddTabbar: + tabbarList.add(new TabbarFileBean(path)); + break; + case TypeDelTabbar: + for (int i = tabbarList.size() - 1; i >= 0; i--) { + if (tabbarList.get(i).getFilePath().length() > path.length()) { + tabbarList.remove(i); + } else { + break; + } + } + break; + case TypeInitTabbar: + if (tabbarList == null) { + tabbarList = new ArrayList<>(); + } else { + tabbarList.clear(); + } + getTabbarFileBeanList(tabbarList, path, allPathsList); + break; + } + if (tabbarAdapter != null) { + tabbarAdapter.updateListData(tabbarList); + tabbarAdapter.notifyDataSetChanged(); + } + return tabbarList; + } +} + diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java new file mode 100644 index 000000000..9fe4cc619 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/Constants.java @@ -0,0 +1,96 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.os.Environment; + +import java.util.HashMap; +import java.util.Map; + +public class Constants { + + public static String DEFAULT_ROOTPATH; + public static String PATH_ANRROID_DATA; + public static String PATH_ANRROID_OBB; + public static final int SORT_NAME_ASC = 0; + public static final int SORT_NAME_DESC = 1; + public static final int SORT_TIME_ASC = 2; + public static final int SORT_TIME_DESC = 3; + public static final int SORT_SIZE_ASC = 4; + public static final int SORT_SIZE_DESC = 5; + public static final int TYPE_CUSTOM_VIEW_NULL = -1; + public static Map mimeTypeMap = null; + + static { + DEFAULT_ROOTPATH = Environment.getExternalStorageDirectory().getAbsolutePath(); + PATH_ANRROID_DATA = DEFAULT_ROOTPATH + "/Android/data"; + PATH_ANRROID_OBB = DEFAULT_ROOTPATH + "/Android/obb"; + } + + static { + mimeTypeMap = new HashMap<>(); + mimeTypeMap.put("apk", "application/vnd.android.package-archive"); + mimeTypeMap.put("asf", "video/x-ms-asf"); + mimeTypeMap.put("avi", "video/x-msvideo"); + mimeTypeMap.put("bin", "application/octet-stream"); + mimeTypeMap.put("bmp", "image/bmp"); + mimeTypeMap.put("c", "text/plain"); + mimeTypeMap.put("class", "application/octet-stream"); + mimeTypeMap.put("conf", "text/plain"); + mimeTypeMap.put("cpp", "text/plain"); + mimeTypeMap.put("doc", "application/msword"); + mimeTypeMap.put("docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + mimeTypeMap.put("xls", "application/vnd.ms-excel"); + mimeTypeMap.put("xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + mimeTypeMap.put("exe", "application/octet-stream"); + mimeTypeMap.put("gif", "image/gif"); + mimeTypeMap.put("gtar", "application/x-gtar"); + mimeTypeMap.put("gz", "application/x-gzip"); + mimeTypeMap.put("h", "text/plain"); + mimeTypeMap.put("htm", "text/html"); + mimeTypeMap.put("html", "text/html"); + mimeTypeMap.put("jar", "application/java-archive"); + mimeTypeMap.put("java", "text/plain"); + mimeTypeMap.put("jpeg", "image/jpeg"); + mimeTypeMap.put("jpg", "image/jpeg"); + mimeTypeMap.put("js", "application/x-javascript"); + mimeTypeMap.put("log", "text/plain"); + mimeTypeMap.put("m3u", "audio/x-mpegurl"); + mimeTypeMap.put("m4a", "audio/mp4a-latm"); + mimeTypeMap.put("m4b", "audio/mp4a-latm"); + mimeTypeMap.put("m4p", "audio/mp4a-latm"); + mimeTypeMap.put("m4u", "video/vnd.mpegurl"); + mimeTypeMap.put("m4v", "video/x-m4v"); + mimeTypeMap.put("mov", "video/quicktime"); + mimeTypeMap.put("mp2", "audio/x-mpeg"); + mimeTypeMap.put("mp3", "audio/mpeg"); + mimeTypeMap.put("mp4", "video/mp4"); + mimeTypeMap.put("mpc", "application/vnd.mpohun.certificate"); + mimeTypeMap.put("mpe", "video/mpeg"); + mimeTypeMap.put("mpeg", "video/mpeg"); + mimeTypeMap.put("mpg", "video/mpeg"); + mimeTypeMap.put("mpg4", "video/mp4"); + mimeTypeMap.put("mpga", "audio/mpeg"); + mimeTypeMap.put("msg", "application/vnd.ms-outlook"); + mimeTypeMap.put("ogg", "audio/ogg"); + mimeTypeMap.put("pdf", "application/pdf"); + mimeTypeMap.put("png", "image/png"); + mimeTypeMap.put("pps", "application/vnd.ms-powerpoint"); + mimeTypeMap.put("ppt", "application/vnd.ms-powerpoint"); + mimeTypeMap.put("pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + mimeTypeMap.put("prop", "text/plain"); + mimeTypeMap.put("rc", "text/plain"); + mimeTypeMap.put("rmvb", "audio/x-pn-realaudio"); + mimeTypeMap.put("rtf", "application/rtf"); + mimeTypeMap.put("sh", "text/plain"); + mimeTypeMap.put("tar", "application/x-tar"); + mimeTypeMap.put("tgz", "application/x-compressed"); + mimeTypeMap.put("txt", "text/plain"); + mimeTypeMap.put("wav", "audio/x-wav"); + mimeTypeMap.put("wma", "audio/x-ms-wma"); + mimeTypeMap.put("wmv", "audio/x-ms-wmv"); + mimeTypeMap.put("wps", "application/vnd.ms-works"); + mimeTypeMap.put("xml", "text/plain"); + mimeTypeMap.put("z", "application/x-compress"); + mimeTypeMap.put("zip", "application/x-zip-compressed"); + mimeTypeMap.put("", "*/*"); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java new file mode 100644 index 000000000..780bee7e5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileBean.java @@ -0,0 +1,153 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.util.Utils; + +import java.io.Serializable; + + +public class FileBean implements Serializable { + private String filePath; + private boolean dir; + private boolean file; + private String fileName; + private String fileExtension; + private int fileImgType; + private String parentPath; + private String parentName; + private int childrenFileNumber; + private int childrenDirNumber; + private String size; + private boolean visible; + private boolean checked; + private long modifyTime; + private long simpleSize; + + public FileBean(String filePath) { + this.filePath = filePath; + visible = false; + checked = false; + if (FileTools.isFile(filePath)) { + file = true; + dir = false; + } else { + file = false; + dir = true; + } + fileName = FileTools.getFileName(filePath); + fileExtension = FileTools.getFileExtension(filePath); + fileImgType = setImageResourceByExtension(fileExtension); + parentPath = FileTools.getParentPath(filePath); + parentName = FileTools.getDirName(filePath); + childrenFileNumber = FileTools.getChildrenNumber(filePath)[0]; + childrenDirNumber = FileTools.getChildrenNumber(filePath)[1]; + modifyTime = FileTools.getFileLastModified(filePath); + if (file) { + size = FileTools.getSize(filePath); + simpleSize = FileTools.getSimpleSize(filePath); + } + } + + public int setImageResourceByExtension(String extension) { + int resourceId; + switch (extension) { + default: + if (dir) { + resourceId = R.drawable.folder; + } else { + resourceId = Utils.getFileIconSuffix(extension); + } + break; + } + return resourceId; + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public void setVisible(boolean visible) { + this.visible = visible; + } + + public void setChecked(boolean checked) { + this.checked = checked; + } + + public String getFilePath() { + return filePath; + } + + public boolean isDir() { + return dir; + } + + public boolean isFile() { + return file; + } + + public String getFileName() { + return fileName; + } + + public String getFileExtension() { + return fileExtension; + } + + public int getFileImgType() { + return fileImgType; + } + + public String getParentPath() { + return parentPath; + } + + public String getParentName() { + return parentName; + } + + public int getChildrenFileNumber() { + return childrenFileNumber; + } + + public int getChildrenDirNumber() { + return childrenDirNumber; + } + + public String getSize() { + return size; + } + + public boolean isVisible() { + return visible; + } + + public boolean isChecked() { + return checked; + } + + public long getModifyTime() { + return modifyTime; + } + + public long getSimpleSize() { + return simpleSize; + } + + public void setIsDir(Boolean dir) { + this.dir = dir; + } + + public void setIsFile(Boolean file) { + this.file = file; + } + + public void setSize(String size) { + this.size = size; + } + + public void setSimpleSize(long simpleSize) { + this.simpleSize = simpleSize; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java new file mode 100644 index 000000000..2ce0f6a31 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListAdapter.java @@ -0,0 +1,89 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.seafile.seadroid2.R; + +import java.util.List; + +public class FileListAdapter extends RecyclerView.Adapter { + private List mListData; + private Context mContext; + private OnFileItemClickListener onItemClickListener; + + public void setOnItemClickListener(OnFileItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public FileListAdapter(Activity context, List listData) { + mListData = listData; + mContext = context; + } + + @Override + public FileListViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(mContext).inflate(R.layout.item_files_list, parent, false); + FileListViewHolder fileListViewHolder = new FileListViewHolder(view); + return fileListViewHolder; + } + + @SuppressLint("StringFormatMatches") + @Override + public void onBindViewHolder(final FileListViewHolder holder, int positon) { + final FileBean fileBean = mListData.get(positon); + holder.checkBoxFile.setChecked(fileBean.isChecked()); + holder.tvFileName.setText(fileBean.getFileName()); + holder.imgvFiletype.setImageResource(fileBean.getFileImgType()); + boolean isFile = fileBean.isFile(); + if (isFile) { + holder.tvFileDetail.setText(String.format(mContext.getString(R.string.folder_file_item_size), fileBean.getSize())); + } else { + holder.tvFileDetail.setText(String.format(mContext.getString(R.string.folder_file_item_describe), + fileBean.getChildrenFileNumber(), fileBean.getChildrenDirNumber())); + } + if (!isFile) { + holder.checkBoxFile.setVisibility(View.VISIBLE); + } else { + holder.checkBoxFile.setVisibility(View.GONE); + } + + holder.checkBoxFile.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.checkBoxClick(holder.checkBoxFile, holder.getAdapterPosition()); + } + } + }); + + holder.layoutRoot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.itemClick(holder.getAdapterPosition()); + } + } + }); + } + + @Override + public int getItemCount() { + if (mListData == null) { + return 0; + } else { + return mListData.size(); + } + } + + public void updateListData(List mListData) { + this.mListData = mListData; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java new file mode 100644 index 000000000..0f0f2db61 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileListViewHolder.java @@ -0,0 +1,27 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.seafile.seadroid2.R; + + +public class FileListViewHolder extends RecyclerView.ViewHolder { + protected ImageView imgvFiletype; + protected TextView tvFileName, tvFileDetail; + protected CheckBox checkBoxFile; + protected RelativeLayout layoutRoot; + + public FileListViewHolder(View itemView) { + super(itemView); + layoutRoot = (RelativeLayout) itemView.findViewById(R.id.ll_root); + imgvFiletype = (ImageView) itemView.findViewById(R.id.iv_file_type_fileitem); + tvFileName = (TextView) itemView.findViewById(R.id.tv_file_name_fileitem); + tvFileDetail = (TextView) itemView.findViewById(R.id.tv_file_detail_fileitem); + checkBoxFile = (CheckBox) itemView.findViewById(R.id.checkbox_file_fileitem); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java new file mode 100644 index 000000000..af62a50d5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/FileTools.java @@ -0,0 +1,229 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.content.Context; +import android.os.storage.StorageManager; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class FileTools { + public static File getFileByPath(String path) { + return StringTools.isEmpty(path) ? null : new File(path); + } + + public static boolean rename(String filePath, String newName) { + return rename(getFileByPath(filePath), newName); + } + + public static boolean rename(File file, String newName) { + if (file == null) { + return false; + } else if (!file.exists()) { + return false; + } else if (StringTools.isEmpty(newName)) { + return false; + } else if (newName.equals(file.getName())) { + return true; + } else { + File newFile = new File(file.getParent() + File.separator + newName); + return !newFile.exists() && file.renameTo(newFile); + } + } + + public static int[] getChildrenNumber(String path) { + return getChildrenNumber(getFileByPath(path)); + } + + public static int[] getChildrenNumber(File file) { + File[] files = file.listFiles(); + int[] numbers = new int[]{0, 0}; + if (files != null) { + for (int i = 0; i < files.length; i++) { + if (files[i].isFile()) { + numbers[0]++; + } else { + numbers[1]++; + } + } + } + return numbers; + } + + public static boolean isDir(String dirPath) { + return isDir(getFileByPath(dirPath)); + } + + public static boolean isDir(File file) { + return file != null && file.exists() && file.isDirectory(); + } + + public static boolean isFile(String filePath) { + return isFile(getFileByPath(filePath)); + } + + public static boolean isFile(File file) { + return file != null && file.exists() && file.isFile(); + } + + public static String getDirName(String filePath) { + if (StringTools.isEmpty(filePath)) { + return ""; + } else { + int lastSep1 = filePath.lastIndexOf(File.separator); + if (lastSep1 == -1) { + return ""; + } else { + int lastSep2 = filePath.substring(0, lastSep1).lastIndexOf(File.separator); + return filePath.substring(lastSep2 + 1, lastSep1); + } + } + } + + public static String getParentPath(File file) { + return file == null ? "" : getParentPath(file.getAbsolutePath()); + } + + public static String getParentPath(String filePath) { + if (StringTools.isEmpty(filePath)) { + return ""; + } else { + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep); + } + } + + public static String getFileName(String filePath) { + if (StringTools.isEmpty(filePath)) { + return ""; + } else { + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + } + + public static String getFileNameNoExtension(String filePath) { + if (StringTools.isEmpty(filePath)) { + return ""; + } else { + int lastPoi = filePath.lastIndexOf(46); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return lastPoi == -1 ? filePath : filePath.substring(0, lastPoi); + } else { + return lastPoi != -1 && lastSep <= lastPoi ? filePath.substring(lastSep + 1, lastPoi) : filePath.substring(lastSep + 1); + } + } + } + + public static String getFileExtension(String filePath) { + if (StringTools.isEmpty(filePath)) { + return ""; + } else { + int lastPoi = filePath.lastIndexOf(46); + int lastSep = filePath.lastIndexOf(File.separator); + return lastPoi != -1 && lastSep < lastPoi ? filePath.substring(lastPoi + 1) : ""; + } + } + + public static long getFileLastModified(String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + public static long getFileLastModified(File file) { + return file == null ? -1L : file.lastModified(); + } + + public static Long getSimpleSize(String filePath) { + return getSimpleSize(getFileByPath(filePath)); + } + + public static Long getSimpleSize(File file) { + + return StringTools.getOnlyNumber(getSize(file)); + } + + public static String getSize(String filePath) { + return getSize(getFileByPath(filePath)); + } + + public static String getSize(File file) { + if (file == null) { + return ""; + } else { + return file.isDirectory() ? getDirSize(file) : getFileSize(file); + } + } + + private static String getDirSize(File dir) { + long len = getDirLength(dir); + return len == -1L ? "" : byte2FitMemorySize(len, 3); + } + + private static String getFileSize(File file) { + long len = getFileLength(file); + return len == -1L ? "" : byte2FitMemorySize(len, 3); + } + + private static long getFileLength(File file) { + return !isFile(file) ? -1L : file.length(); + } + + private static long getDirLength(File dir) { + if (!isDir(dir)) { + return 0L; + } else { + long len = 0L; + File[] files = dir.listFiles(); + if (files != null && files.length > 0) { + File[] var4 = files; + int var5 = files.length; + + for (int var6 = 0; var6 < var5; ++var6) { + File file = var4[var6]; + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + } + + public static String byte2FitMemorySize(long byteSize, int precision) { + if (precision < 0) { + throw new IllegalArgumentException("precision shouldn't be less than zero!"); + } else if (byteSize < 0L) { + throw new IllegalArgumentException("byteSize shouldn't be less than zero!"); + } else if (byteSize < 1024L) { + return String.format("%." + precision + "fB", (double) byteSize); + } else if (byteSize < 1048576L) { + return String.format("%." + precision + "fKB", (double) byteSize / 1024.0D); + } else { + return byteSize < 1073741824L ? String.format("%." + precision + "fMB", (double) byteSize / 1048576.0D) : String.format("%." + precision + "fGB", (double) byteSize / 1.073741824E9D); + } + } + + public static List getAllPaths(Context context) { + Method mMethodGetPaths = null; + String[] paths = null; + StorageManager mStorageManager = (StorageManager) context + .getSystemService(context.STORAGE_SERVICE);//storage + try { + mMethodGetPaths = mStorageManager.getClass().getMethod("getVolumePaths"); + paths = (String[]) mMethodGetPaths.invoke(mStorageManager); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (paths != null) { + return Arrays.asList(paths); + } + return new ArrayList(); + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java new file mode 100644 index 000000000..edd6acbb6 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/OnFileItemClickListener.java @@ -0,0 +1,8 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.view.View; + +public interface OnFileItemClickListener { + void itemClick(int position); + void checkBoxClick(View view, int position); +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java new file mode 100644 index 000000000..ae1fc9d74 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectBackupFolderFragment.java @@ -0,0 +1,194 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.app.Activity; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.Toast; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class SelectBackupFolderFragment extends Fragment { + + private RecyclerView mTabbarFileRecyclerView, mFileRecyclerView; + private SelectOptions mSelectOptions; + private List allPathsList; + private List mShowFileTypes; + private int mSortType; + private List mFileList; + private List mTabbarFileList; + private String mCurrentPath; + private FileListAdapter mFileListAdapter; + private TabbarFileListAdapter mTabbarFileListAdapter; + private FolderBackupConfigActivity mActivity; + private boolean chooseDirPage; + private Button mButton; + private List selectPaths; + private String initialPath; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + mActivity = (FolderBackupConfigActivity) getActivity(); + View rootView = getActivity().getLayoutInflater().inflate(R.layout.folder_selection_fragment, null); + mTabbarFileRecyclerView = (RecyclerView) rootView.findViewById(R.id.rcv_tabbar_files_list); + mFileRecyclerView = (RecyclerView) rootView.findViewById(R.id.rcv_files_list); + mButton = (Button) rootView.findViewById(R.id.bt_dir_click_to_finish); + mFileRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), + LinearLayoutManager.VERTICAL, false)); + mFileListAdapter = new FileListAdapter(getActivity(), mFileList); + mFileRecyclerView.setAdapter(mFileListAdapter); + mTabbarFileRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity(), + LinearLayoutManager.HORIZONTAL, false)); + mTabbarFileListAdapter = new TabbarFileListAdapter(getActivity(), mTabbarFileList); + mTabbarFileRecyclerView.setAdapter(mTabbarFileListAdapter); + chooseDirPage = mActivity.isChooseDirPage(); + init(); + initData(); + return rootView; + } + + private void init() { + if (chooseDirPage) { + mButton.setVisibility(View.VISIBLE); + } else { + mButton.setVisibility(View.GONE); + } + selectPaths = mActivity.getSelectFolderPath(); + if (selectPaths == null) { + selectPaths = new ArrayList<>(); + } + + mButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + mActivity.saveFolderConfig(); + mActivity.finish(); + } + }); + + mFileListAdapter.setOnItemClickListener(new OnFileItemClickListener() { + @Override + public void itemClick(int position) { + FileBean item = mFileList.get(position); + if (item.isFile()) { + Toast.makeText(getActivity(), getActivity().getString(R.string.selection_file_type), Toast.LENGTH_SHORT).show(); + } else { + mCurrentPath = item.getFilePath(); + refreshFileAndTabbar(BeanListManager.TypeAddTabbar); + } + } + + @Override + public void checkBoxClick(View view, int position) { + FileBean item = mFileList.get(position); + for (FileBean fb : mFileList) { + if (item.equals(fb)) { + if (fb.isChecked()) { + for (int i = 0; i < selectPaths.size(); i++) { + if (item.getFilePath().equals(selectPaths.get(i))) { + selectPaths.remove(i); + i--; + } + } + fb.setChecked(false); + + } else { + selectPaths.add(item.getFilePath()); + fb.setChecked(true); + } + mActivity.setFolderPathList(selectPaths); + } + } + view.post(new Runnable() { + @Override + public void run() { + mFileListAdapter.updateListData(mFileList); + mFileListAdapter.notifyDataSetChanged(); + } + }); + } + }); + + mTabbarFileListAdapter.setOnItemClickListener(new OnFileItemClickListener() { + @Override + public void itemClick(int position) { + TabbarFileBean item = mTabbarFileList.get(position); + mCurrentPath = item.getFilePath(); + + if (mTabbarFileList.size() > 1) { + refreshFileAndTabbar(BeanListManager.TypeDelTabbar); + } + } + + @Override + public void checkBoxClick(View view, int position) { + + } + }); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getActivity().finish(); + } + + @Override + public void onPause() { + super.onPause(); + getActivity().finish(); + } + + private void initData() { + mSelectOptions = SelectOptions.getResetInstance(getActivity()); + allPathsList= initRootPath(getActivity()); + mShowFileTypes = Arrays.asList(mSelectOptions.getShowFileTypes()); + mSortType = mSelectOptions.getSortType(); + mFileList = new ArrayList<>(); + mTabbarFileList = new ArrayList<>(); + refreshFileAndTabbar(BeanListManager.TypeInitTabbar); + } + + private List initRootPath(Activity activity) { + List allPaths = FileTools.getAllPaths(activity); + mCurrentPath = mSelectOptions.rootPath; + if (mCurrentPath == null) { + if (allPaths.isEmpty()) { + mCurrentPath = Constants.DEFAULT_ROOTPATH; + } else { + mCurrentPath = allPaths.get(0); + } + } + initialPath =mCurrentPath; + return allPaths; + } + + private void refreshFileAndTabbar(int tabbarType) { + BeanListManager.upDataFileBeanListByAsyn(getActivity(), selectPaths, mFileList, mFileListAdapter, + mCurrentPath, mShowFileTypes, mSortType); + BeanListManager.upDataTabbarFileBeanList(mTabbarFileList, mTabbarFileListAdapter, + mCurrentPath, tabbarType, allPathsList); + } + + public boolean onBackPressed() { + if (mCurrentPath.equals(initialPath)||allPathsList.contains(mCurrentPath)){ + return false; + }else { + mCurrentPath=FileTools.getParentPath(mCurrentPath); + refreshFileAndTabbar(BeanListManager.TypeDelTabbar); + return true; + } + } + +} + diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java new file mode 100644 index 000000000..9264ad74e --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/SelectOptions.java @@ -0,0 +1,119 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.app.Fragment; +import android.app.FragmentManager; +import android.content.Context; +import android.graphics.Color; +import android.support.v4.content.ContextCompat; +import android.view.View; + +import com.seafile.seadroid2.R; + +import java.util.List; + +public class SelectOptions { + + private static SelectOptions mSelectOptions; + public Integer requestCode; + public Integer frameLayoutID; + private Context mContext; + public String[] mShowFileTypes; + public String[] mSelectFileTypes; + public Integer mSortType; + public Boolean mSingle; + public Integer mMaxCount; + public Fragment mToolbarFragment; + public Fragment mMoreChooseFragment; + public Boolean onlyShowImages; + public Boolean onlyShowVideos; + public Boolean needMoreOptions; + public Boolean needMoreChoose; + public String[] optionsName; + public String[] MoreChooseItemName; + public boolean[] optionsNeedCallBack; + public boolean[] toolbarViewNeedCallBack; + public boolean[] moreChooseItemNeedCallBack; + public onToolbarListener[] toolbarListeners; + public String rootPath; + public String toolbarMainTitle; + public String toolbarSubtitleTitle; + public Integer toolbarBG; + public Integer toolbarMainTitleColor; + public Integer toolbarSubtitleColor; + public Integer toolbarOptionColor; + public Integer toolbarOptionSize; + public FragmentManager fragmentManager; + public Boolean showToolBarFragment; + public Integer typeLoadCustomView; + + public static SelectOptions getInstance() { + if (mSelectOptions == null) { + mSelectOptions = new SelectOptions(); + } + return mSelectOptions; + } + + public static SelectOptions getResetInstance(Context context) { + mSelectOptions = getInstance(); + mSelectOptions.mContext = context; + mSelectOptions.reset(); + return mSelectOptions; + } + + public Context getContext() { + return mContext; + } + + public String[] getShowFileTypes() { + if (mShowFileTypes == null) { + return new String[]{}; + } + return mShowFileTypes; + } + + public int getSortType() { + if (mSortType == null) { + return Constants.SORT_NAME_ASC; + } + return mSortType; + } + + private void reset() { + requestCode = 100; + frameLayoutID = null; + mShowFileTypes = null; + mSelectFileTypes = null; + mSortType = Constants.SORT_NAME_ASC; + mSingle = true; + mMaxCount = 1; + mToolbarFragment = null; + mMoreChooseFragment = null; + onlyShowImages = false; + onlyShowVideos = false; + needMoreOptions = false; + needMoreChoose = false; + optionsName = null; + MoreChooseItemName = null; + optionsNeedCallBack = null; + toolbarViewNeedCallBack = null; + moreChooseItemNeedCallBack = null; + toolbarListeners = null; + rootPath = null; + toolbarMainTitle = ""; + toolbarSubtitleTitle = null; + toolbarBG = ContextCompat.getColor(mContext, R.color.fancy_orange); + toolbarMainTitleColor = Color.WHITE; + toolbarSubtitleColor = Color.WHITE; + toolbarOptionColor = Color.WHITE; + toolbarOptionSize = 18; + fragmentManager = null; + showToolBarFragment = true; + typeLoadCustomView = Constants.TYPE_CUSTOM_VIEW_NULL; + } + + public interface onToolbarListener { + void onClick(View view, String currentPath, List fileBeanList, + List callBackData, TabbarFileListAdapter tabbarAdapter, + FileListAdapter fileAdapter, List callBackFileBeanList); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java new file mode 100644 index 000000000..03f418ab5 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/StringTools.java @@ -0,0 +1,55 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.text.TextUtils; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; +import com.seafile.seadroid2.SettingsManager; +import com.seafile.seadroid2.util.Utils; + +import java.util.ArrayList; +import java.util.List; + +public class StringTools { + + public static boolean isEmpty(String s) { + return s == null || s.trim().isEmpty() ? true : false; + } + + public static Long getOnlyNumber(String s) { + String temp = s.replaceAll("[^0-9]", ""); + if (temp.equals("")) { + return -1L; + } + Long number = Long.valueOf(temp); + if (number == null) { + return -1L; + } + return Long.valueOf(temp); + } + + public static List getJsonToList(String strJson) { + List list = new ArrayList(); + if (TextUtils.isEmpty(strJson)) { + return list; + } + Gson gson = new Gson(); + list = gson.fromJson(strJson, new TypeToken>() { + }.getType()); + return list; + + } + + public static boolean checkFolderUploadNetworkAvailable() { + if (!Utils.isNetworkOn()) { + return false; + } + // user does not allow mobile connections + if (!Utils.isWiFiOn() && !SettingsManager.instance().isFolderBackupDataPlanAllowed()) { + return false; + } + // Wi-Fi or 2G/3G/4G connections available + return true; + } + +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileBean.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileBean.java new file mode 100644 index 000000000..5a50e8353 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileBean.java @@ -0,0 +1,43 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + + +public class TabbarFileBean { + + private String filePath; + private String fileName; + private String fileNameNoExtension; + private String parentPath; + private String parentName; + + public TabbarFileBean(String filePath) { + this.filePath = filePath; + fileName = FileTools.getFileName(filePath); + fileNameNoExtension = FileTools.getFileNameNoExtension(filePath); + parentPath = FileTools.getParentPath(filePath); + parentName = FileTools.getDirName(filePath); + } + + public TabbarFileBean(String filePath, String fileName) { + this.filePath = filePath; + this.fileName = fileName; + fileNameNoExtension = fileName; + parentPath = FileTools.getParentPath(filePath); + parentName = FileTools.getDirName(filePath); + } + + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + public String getFilePath() { + return filePath; + } + + public String getFileName() { + return fileName; + } + + public String getParentPath() { + return parentPath; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileListAdapter.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileListAdapter.java new file mode 100644 index 000000000..69cc53701 --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileListAdapter.java @@ -0,0 +1,62 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.app.Activity; +import android.content.Context; +import android.support.v7.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.seafile.seadroid2.R; + +import java.util.List; + +public class TabbarFileListAdapter extends RecyclerView.Adapter { + + private List mListData; + private Context mContext; + private OnFileItemClickListener onItemClickListener; + + public void setOnItemClickListener(OnFileItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public TabbarFileListAdapter(Activity context, List listData) { + mListData = listData; + mContext = context; + } + + @Override + public TabbarFileViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(mContext).inflate(R.layout.item_tabbar_files_list, parent, false); + return new TabbarFileViewHolder(view); + } + + @Override + public void onBindViewHolder(final TabbarFileViewHolder holder, int positon) { + final TabbarFileBean entity = mListData.get(positon); + String fileName = entity.getFileName().replaceAll("[^\\p{Print}]", ""); + holder.tvName.setText(fileName); + holder.llRoot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.itemClick(holder.getAdapterPosition()); + } + } + }); + } + + @Override + public int getItemCount() { + if (mListData == null) { + return 0; + } else { + return mListData.size(); + } + } + + public void updateListData(List mListData) { + this.mListData = mListData; + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java new file mode 100644 index 000000000..8d5640c7d --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/folderbackup/selectfolder/TabbarFileViewHolder.java @@ -0,0 +1,21 @@ +package com.seafile.seadroid2.folderbackup.selectfolder; + +import android.support.v7.widget.RecyclerView; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.seafile.seadroid2.R; + + +public class TabbarFileViewHolder extends RecyclerView.ViewHolder { + + protected TextView tvName; + protected LinearLayout llRoot; + + public TabbarFileViewHolder(View itemView) { + super(itemView); + llRoot = (LinearLayout) itemView.findViewById(R.id.ll_root); + tvName = (TextView) itemView.findViewById(R.id.btn_item_tabbar); + } +} diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/DownloadTask.java b/app/src/main/java/com/seafile/seadroid2/transfer/DownloadTask.java index bfa9fb264..91af019a3 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/DownloadTask.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/DownloadTask.java @@ -25,7 +25,7 @@ public class DownloadTask extends TransferTask { public DownloadTask(int taskID, Account account, String repoName, String repoID, String path, DownloadStateListener downloadStateListener) { - super(taskID, account, repoName, repoID, path); + super("", taskID, account, repoName, repoID, path); this.downloadStateListener = downloadStateListener; } diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java b/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java index 859fa1889..3a6924645 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/TransferManager.java @@ -4,9 +4,11 @@ import com.google.common.collect.Lists; import com.seafile.seadroid2.SeadroidApplication; +import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.data.CameraSyncEvent; import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; +import com.seafile.seadroid2.util.Utils; import org.greenrobot.eventbus.EventBus; @@ -46,6 +48,11 @@ public abstract class TransferManager { */ protected List waitingList = Lists.newArrayList(); + private int folderBackupWaitingNumber; + private int folderBackupTotalNumber; + private int cameraUploadWaitingNumber; + private int cameraUploadTotalNumber; + protected synchronized TransferTask getTask(int taskID) { return allTaskList.get(taskID); } @@ -97,13 +104,52 @@ public synchronized void doNext() { TransferTask task = waitingList.remove(0); transferringList.add(task); - SeadroidApplication.getInstance().setCameraUploadNumber(waitingList.size(), allTaskList.size()); - SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.UPLOADING); - EventBus.getDefault().post(new CameraSyncEvent("upload")); + int scanUploadStatus = SeadroidApplication.getInstance().getScanUploadStatus(); + getWaitingNumber(); + getTotalNumber(); + if (scanUploadStatus > 0) { + SeadroidApplication.getInstance().setCameraUploadNumber(cameraUploadWaitingNumber, cameraUploadTotalNumber); + SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.UPLOADING); + EventBus.getDefault().post(new CameraSyncEvent("upload")); + } + if (SettingsManager.instance().isFolderAutomaticBackup()) { + SeadroidApplication.getInstance().setFolderBackupNumber(folderBackupTotalNumber, folderBackupWaitingNumber); + } + + ConcurrentAsyncTask.execute(task); } } + public void getWaitingNumber() { + cameraUploadWaitingNumber = 0; + folderBackupWaitingNumber = 0; + + for (TransferTask waitTask : waitingList) { + if (waitTask.getSource().equals(Utils.TRANSFER_PHOTO_TAG)) { + cameraUploadWaitingNumber++; + } + if (waitTask.getSource().equals(Utils.TRANSFER_FOLDER_TAG)) { + folderBackupWaitingNumber++; + } + } + } + + public void getTotalNumber() { + cameraUploadTotalNumber = 0; + folderBackupTotalNumber = 0; + + for (TransferTask allTask : allTaskList.values()) { + if (allTask.getSource().equals(Utils.TRANSFER_PHOTO_TAG)) { + cameraUploadTotalNumber++; + } + if (allTask.getSource().equals(Utils.TRANSFER_FOLDER_TAG)) { + folderBackupTotalNumber++; + } + + } + } + protected void cancel(int taskID) { TransferTask task = getTask(taskID); if (task != null) { diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/TransferService.java b/app/src/main/java/com/seafile/seadroid2/transfer/TransferService.java index f14dcc803..79fc80e43 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/TransferService.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/TransferService.java @@ -5,9 +5,11 @@ import android.os.Binder; import android.os.IBinder; import android.util.Log; + import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.notification.DownloadNotificationProvider; import com.seafile.seadroid2.notification.UploadNotificationProvider; +import com.seafile.seadroid2.util.Utils; import java.util.List; @@ -90,7 +92,11 @@ public IBinder onBind(Intent intent) { * @return */ public int addTaskToUploadQue(Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal) { - return uploadTaskManager.addTaskToQue(account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal,false); + return uploadTaskManager.addTaskToQue("", account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, false); + } + + public int addTaskToSourceQue(String source, Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal) { + return uploadTaskManager.addTaskToQue(source, account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, false); } /** @@ -110,7 +116,7 @@ public int addTaskToUploadQue(Account account, String repoID, String repoName, S */ public int addTaskToUploadQueBlock(Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal) { - return uploadTaskManager.addTaskToQue(account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, true); + return uploadTaskManager.addTaskToQue("", account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, true); } /** @@ -131,6 +137,11 @@ public int addUploadTask(Account account, String repoID, String repoName, String return addTaskToUploadQue(account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal); } + public int addCameraUploadTask(Account account, String repoID, String repoName, String dir, + String filePath, boolean isUpdate, boolean isCopyToLocal) { + return addTaskToSourceQue(Utils.TRANSFER_PHOTO_TAG, account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal); + } + public UploadTaskInfo getUploadTaskInfo(int taskID) { return (UploadTaskInfo) uploadTaskManager.getTaskInfo(taskID); } diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java b/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java index c02b8fa68..048ffb8b4 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/TransferTask.java @@ -22,8 +22,9 @@ public abstract class TransferTask extends AsyncTask { protected long totalSize, finished; protected TaskState state; protected SeafException err; + protected String source; - public TransferTask(int taskID, Account account, String repoName, String repoID, String path) { + public TransferTask(String source, int taskID, Account account, String repoName, String repoID, String path) { this.account = account; this.repoName = repoName; this.repoID = repoID; @@ -33,6 +34,7 @@ public TransferTask(int taskID, Account account, String repoName, String repoID, // The size of the file would be known in the first progress update this.totalSize = -1; this.taskID = taskID; + this.source = source; } protected void cancel() { @@ -53,6 +55,10 @@ public int getTaskID() { return taskID; } + public String getSource() { + return source; + } + public TaskState getState() { return state; } diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java index ff55ec78e..8f0d5e62e 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTask.java @@ -28,14 +28,15 @@ public class UploadTask extends TransferTask { private boolean isCopyToLocal; // false to turn off copy operation private boolean byBlock; private UploadStateListener uploadStateListener; + private String source; private DataManager dataManager; public static final int HTTP_ABOVE_QUOTA = 443; - public UploadTask(int taskID, Account account, String repoID, String repoName, + public UploadTask(String source, int taskID, Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal, boolean byBlock, UploadStateListener uploadStateListener) { - super(taskID, account, repoName, repoID, filePath); + super(source, taskID, account, repoName, repoID, filePath); this.dir = dir; this.isUpdate = isUpdate; this.isCopyToLocal = isCopyToLocal; @@ -44,6 +45,7 @@ public UploadTask(int taskID, Account account, String repoID, String repoName, this.totalSize = new File(filePath).length(); this.finished = 0; this.dataManager = new DataManager(account); + this.source = source; } public UploadTaskInfo getTaskInfo() { diff --git a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTaskManager.java b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTaskManager.java index ebce9c544..ae347542d 100644 --- a/app/src/main/java/com/seafile/seadroid2/transfer/UploadTaskManager.java +++ b/app/src/main/java/com/seafile/seadroid2/transfer/UploadTaskManager.java @@ -25,12 +25,12 @@ public class UploadTaskManager extends TransferManager implements UploadStateLis private static UploadNotificationProvider mNotifyProvider; - public int addTaskToQue(Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal, boolean byBlock) { + public int addTaskToQue(String source, Account account, String repoID, String repoName, String dir, String filePath, boolean isUpdate, boolean isCopyToLocal, boolean byBlock) { if (repoID == null || repoName == null) return 0; // create a new one to avoid IllegalStateException - UploadTask task = new UploadTask(++notificationID, account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, byBlock, this); + UploadTask task = new UploadTask(source, ++notificationID, account, repoID, repoName, dir, filePath, isUpdate, isCopyToLocal, byBlock, this); addTaskToQue(task); return task.getTaskID(); } @@ -54,7 +54,7 @@ public void retry(int taskID) { UploadTask task = (UploadTask) getTask(taskID); if (task == null || !task.canRetry()) return; - addTaskToQue(task.getAccount(), task.getRepoID(), task.getRepoName(), task.getDir(), task.getPath(), task.isUpdate(), task.isCopyToLocal(),false); + addTaskToQue(task.getSource(), task.getAccount(), task.getRepoID(), task.getRepoName(), task.getDir(), task.getPath(), task.isUpdate(), task.isCopyToLocal(),false); } private void notifyProgress(int taskID) { diff --git a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java index 33b2f1320..160099055 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/activity/BrowserActivity.java @@ -31,6 +31,7 @@ import android.support.v4.view.ViewPager; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; @@ -46,6 +47,8 @@ import com.seafile.seadroid2.SettingsManager; import com.seafile.seadroid2.account.Account; import com.seafile.seadroid2.account.AccountManager; +import com.seafile.seadroid2.folderbackup.FolderBackupService; +import com.seafile.seadroid2.folderbackup.FolderBackupService.FileBackupBinder; import com.seafile.seadroid2.cameraupload.CameraUploadManager; import com.seafile.seadroid2.cameraupload.MediaObserverService; import com.seafile.seadroid2.data.CheckUploadServiceEvent; @@ -158,6 +161,7 @@ public class BrowserActivity extends BaseActivity private DataManager dataManager = null; private TransferService txService = null; + private FolderBackupService mFolderBackupService = null; private TransferReceiver mTransferReceiver; private AccountManager accountManager; private int currentPosition = 0; @@ -229,11 +233,19 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); accountManager = new AccountManager(this); - EventBus.getDefault().register(this); + // restart service should it have been stopped for some reason Intent mediaObserver = new Intent(this, MediaObserverService.class); startService(mediaObserver); + Intent dIntent = new Intent(this, FolderBackupService.class); + startService(dIntent); + Log.d(DEBUG_TAG, "----start FolderBackupService"); + + Intent dirIntent = new Intent(this, FolderBackupService.class); + bindService(dirIntent, folderBackupConnection, Context.BIND_AUTO_CREATE); + Log.d(DEBUG_TAG, "----try bind FolderBackupService"); + if (!isTaskRoot()) { final Intent intent = getIntent(); final String intentAction = getIntent().getAction(); @@ -812,10 +824,31 @@ public void onServiceDisconnected(ComponentName arg0) { } }; + ServiceConnection folderBackupConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + FileBackupBinder binder = (FileBackupBinder) service; + mFolderBackupService = binder.getService(); + Log.d(DEBUG_TAG, "-----bind FolderBackupService"); + boolean dirAutomaticUpload = SettingsManager.instance().isFolderAutomaticBackup(); + String backupEmail = SettingsManager.instance().getBackupEmail(); + if (dirAutomaticUpload && mFolderBackupService != null && !TextUtils.isEmpty(backupEmail)) { + mFolderBackupService.folderBackup(backupEmail); + } + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mFolderBackupService = null; + } + }; + @Override public void onStart() { Log.d(DEBUG_TAG, "onStart"); super.onStart(); + EventBus.getDefault().register(this); + if (android.os.Build.VERSION.SDK_INT < 14 && SettingsManager.instance().isGestureLockRequired()) { Intent intent = new Intent(this, UnlockGesturePasswordActivity.class); @@ -875,6 +908,7 @@ protected void onNewIntent(Intent intent) { protected void onStop() { Log.d(DEBUG_TAG, "onStop"); super.onStop(); + EventBus.getDefault().unregister(this); if (mTransferReceiver != null) { LocalBroadcastManager.getInstance(this).unregisterReceiver(mTransferReceiver); @@ -884,11 +918,15 @@ protected void onStop() { @Override protected void onDestroy() { Log.d(DEBUG_TAG, "onDestroy is called"); - EventBus.getDefault().unregister(this); + if (txService != null) { unbindService(mConnection); txService = null; } + if (mFolderBackupService != null) { + unbindService(folderBackupConnection); + mFolderBackupService = null; + } super.onDestroy(); } @@ -2455,5 +2493,11 @@ public void onEvent(CheckUploadServiceEvent result) { Log.d(DEBUG_TAG, "FileMonitorService============false "); } + if (!Utils.isServiceRunning(BrowserActivity.this, "com.seafile.seadroid2.folderbackup.FolderBackupService")) { + monitorIntent = new Intent(this, FolderBackupService.class); + startService(monitorIntent); + Log.d(DEBUG_TAG, "FolderBackupService============false "); + } + } } diff --git a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java b/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java index 3bfe7cbc2..fdf9d7cdb 100644 --- a/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/com/seafile/seadroid2/ui/fragment/SettingsFragment.java @@ -21,6 +21,8 @@ import com.bumptech.glide.Glide; import com.google.common.collect.Maps; +import com.hjq.permissions.OnPermissionCallback; +import com.hjq.permissions.XXPermissions; import com.seafile.seadroid2.R; import com.seafile.seadroid2.SeadroidApplication; import com.seafile.seadroid2.SeafException; @@ -36,6 +38,11 @@ import com.seafile.seadroid2.data.DatabaseHelper; import com.seafile.seadroid2.data.ServerInfo; import com.seafile.seadroid2.data.StorageManager; +import com.seafile.seadroid2.folderbackup.FolderBackupConfigActivity; +import com.seafile.seadroid2.folderbackup.FolderBackupDBHelper; +import com.seafile.seadroid2.folderbackup.FolderBackupEvent; +import com.seafile.seadroid2.folderbackup.RepoConfig; +import com.seafile.seadroid2.folderbackup.selectfolder.StringTools; import com.seafile.seadroid2.gesturelock.LockPatternUtils; import com.seafile.seadroid2.ui.activity.BrowserActivity; import com.seafile.seadroid2.ui.activity.CreateGesturePasswordActivity; @@ -46,6 +53,7 @@ import com.seafile.seadroid2.ui.dialog.ClearPasswordTaskDialog; import com.seafile.seadroid2.ui.dialog.SwitchStorageTaskDialog; import com.seafile.seadroid2.ui.dialog.TaskDialog.TaskDialogListener; +import com.seafile.seadroid2.util.CameraSyncStatus; import com.seafile.seadroid2.util.ConcurrentAsyncTask; import com.seafile.seadroid2.util.Utils; @@ -65,9 +73,12 @@ public class SettingsFragment extends CustomPreferenceFragment { public static final String CAMERA_UPLOAD_BOTH_PAGES = "com.seafile.seadroid2.camera.upload"; public static final String CAMERA_UPLOAD_REMOTE_LIBRARY = "com.seafile.seadroid2.camera.upload.library"; + public static final String FOLDER_BACKUP_REMOTE_PATH = "com.seafile.seadroid2.folder.backup.path"; public static final String CAMERA_UPLOAD_LOCAL_DIRECTORIES = "com.seafile.seadroid2.camera.upload.directories"; public static final String CONTACTS_UPLOAD_REMOTE_LIBRARY = "com.seafile.seadroid2.contacts.upload.library"; + public static final String FOLDER_BACKUP_REMOTE_LIBRARY = "com.seafile.seadroid2.folder.backup.library"; public static final int CHOOSE_CAMERA_UPLOAD_REQUEST = 2; + public static final int CHOOSE_BACKUP_UPLOAD_REQUEST = 5; // public static final int CHOOSE_CONTACTS_UPLOAD_REQUEST = 3; // Account Info private static Map accountInfoMap = Maps.newHashMap(); @@ -97,7 +108,17 @@ public class SettingsFragment extends CustomPreferenceFragment { // private Preference cContactsRepoBackUp; // private Preference cContactsRepoRecovery; private long mMtime; - private Preference cUploadRepoState; + + private PreferenceCategory cFolderBackupCategory; + private Preference cBackupRepoState; + private Preference cBackupFolderMode; + private Preference cBackupFolderRepo; + private Preference cBackupFolderPref; + private Preference cBackupFolderState; + private Account act; + private List backupSelectPaths; + private FolderBackupDBHelper databaseHelper; + private RepoConfig selectRepoConfig; @Override public void onAttach(Activity activity) { @@ -110,21 +131,25 @@ public void onAttach(Activity activity) { accountMgr = new AccountManager(mActivity); cameraManager = new CameraUploadManager(mActivity.getApplicationContext()); // contactsManager = new ContactsUploadManager(mActivity.getApplicationContext()); - Account act = accountMgr.getCurrentAccount(); + act = accountMgr.getCurrentAccount(); dataMgr = new DataManager(act); + databaseHelper = FolderBackupDBHelper.getDatabaseHelper(); } public void onCreate(Bundle savedInstanceState) { Log.d(DEBUG_TAG, "onCreate"); super.onCreate(savedInstanceState); - EventBus.getDefault().register(this); + settingsMgr.registerSharedPreferencesListener(settingsListener); Account account = accountMgr.getCurrentAccount(); if (!Utils.isNetworkOn()) { mActivity.showShortToast(mActivity, R.string.network_down); return; } - + String backupPaths = SettingsManager.instance().getBackupPaths(); + if (!TextUtils.isEmpty(backupPaths)) { + backupSelectPaths = StringTools.getJsonToList(backupPaths); + } ConcurrentAsyncTask.execute(new RequestAccountInfoTask(), account); } @@ -132,7 +157,7 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onDestroy() { super.onDestroy(); - EventBus.getDefault().unregister(this); + Log.d(DEBUG_TAG, "onDestroy()"); settingsMgr.unregisterSharedPreferencesListener(settingsListener); } @@ -269,11 +294,54 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { cPrivacyCategory.removePreference(clientEncPref); } } + + cFolderBackupCategory = (PreferenceCategory) findPreference(SettingsManager.FOLDER_BACKUP_CATEGORY_KEY); // Camera Upload cUploadCategory = (PreferenceCategory) findPreference(SettingsManager.CAMERA_UPLOAD_CATEGORY_KEY); cUploadAdvancedScreen = (PreferenceScreen) findPreference(SettingsManager.CAMERA_UPLOAD_ADVANCED_SCREEN_KEY); cUploadAdvancedCategory = (PreferenceCategory) findPreference(SettingsManager.CAMERA_UPLOAD_ADVANCED_CATEGORY_KEY); + findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + + if (newValue instanceof Boolean) { + boolean isChecked = (Boolean) newValue; + if (!isChecked) { + cFolderBackupCategory.removePreference(cBackupFolderMode); + cFolderBackupCategory.removePreference(cBackupFolderRepo); + cFolderBackupCategory.removePreference(cBackupFolderPref); + cFolderBackupCategory.removePreference(cBackupFolderState); + SettingsManager.instance().saveFolderAutomaticBackup(false); + } else { + XXPermissions.with(getActivity()).permission("android.permission.MANAGE_EXTERNAL_STORAGE").request(new OnPermissionCallback() { + + @Override + public void onGranted(List permissions, boolean all) { + if (all) { + SettingsManager.instance().saveFolderAutomaticBackup(true); + refreshCameraUploadView(); + + } + } + + @Override + public void onDenied(List permissions, boolean never) { + if (never) { + Toast.makeText(getActivity(), mActivity.getString(R.string.authorization_storage_permission), Toast.LENGTH_LONG).show(); + XXPermissions.startPermissionActivity(getActivity(), permissions); + } else { + Toast.makeText(getActivity(), mActivity.getString(R.string.get_storage_permission_failed), Toast.LENGTH_LONG).show(); + ((CheckBoxPreference) findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY)).setChecked(false); + } + } + }); + } + return true; + } + return false; + } + }); findPreference(SettingsManager.CAMERA_UPLOAD_SWITCH_KEY).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @Override public boolean onPreferenceChange(Preference preference, Object newValue) { @@ -309,9 +377,45 @@ public boolean onPreferenceClick(Preference preference) { return true; } }); + // Change backup folder + cBackupFolderMode = findPreference(SettingsManager.FOLDER_BACKUP_MODE); + cBackupFolderRepo = findPreference(SettingsManager.FOLDER_BACKUP_LIBRARY_KEY); + cBackupFolderPref = findPreference(SettingsManager.SELECTED_BACKUP_FOLDERS_KEY); + cBackupFolderState = findPreference(SettingsManager.FOLDER_BACKUP_STATE); + cBackupFolderMode.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + showWifiDialog(); + return true; + } + }); + cBackupFolderRepo.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { + + // choose remote library + Intent intent = new Intent(mActivity, FolderBackupConfigActivity.class); + intent.putExtra(FOLDER_BACKUP_REMOTE_LIBRARY, true); + startActivityForResult(intent, CHOOSE_BACKUP_UPLOAD_REQUEST); + return true; + } + }); + cBackupFolderPref.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(Preference preference) { - cUploadRepoState = findPreference(SettingsManager.CAMERA_UPLOAD_STATE); - cUploadRepoState.setSummary(Utils.getUploadStateShow(getActivity())); + // choose remote folder path + Intent intent = new Intent(mActivity, FolderBackupConfigActivity.class); + intent.putExtra(FOLDER_BACKUP_REMOTE_PATH, true); + startActivityForResult(intent, CHOOSE_BACKUP_UPLOAD_REQUEST); + return true; + } + }); + + cBackupRepoState = findPreference(SettingsManager.CAMERA_UPLOAD_STATE); + if (cameraManager.isCameraUploadEnabled()) { + cBackupRepoState.setSummary(Utils.getUploadStateShow(getActivity())); + } // Contacts Upload // cContactsCategory = (PreferenceCategory) findPreference(SettingsManager.CONTACTS_UPLOAD_CATEGORY_KEY); @@ -613,12 +717,46 @@ private void updateStorageLocationSummary() { private void refreshCameraUploadView() { Account camAccount = cameraManager.getCameraAccount(); + String backupEmail = SettingsManager.instance().getBackupEmail(); if (camAccount != null && settingsMgr.getCameraUploadRepoName() != null) { cUploadRepoPref.setSummary(camAccount.getSignature() + "/" + settingsMgr.getCameraUploadRepoName()); } ((CheckBoxPreference) findPreference(SettingsManager.CAMERA_UPLOAD_SWITCH_KEY)).setChecked(cameraManager.isCameraUploadEnabled()); + ((CheckBoxPreference) findPreference(SettingsManager.FOLDER_BACKUP_SWITCH_KEY)).setChecked(SettingsManager.instance().isFolderAutomaticBackup()); + + if (SettingsManager.instance().isFolderAutomaticBackup()) { + cFolderBackupCategory.addPreference(cBackupFolderMode); + cFolderBackupCategory.addPreference(cBackupFolderRepo); + cFolderBackupCategory.addPreference(cBackupFolderPref); + cFolderBackupCategory.addPreference(cBackupFolderState); + if (backupSelectPaths == null || backupSelectPaths.size() == 0) { + cBackupFolderPref.setSummary("0"); + } else { + cBackupFolderPref.setSummary(backupSelectPaths.size() + ""); + } + Utils.utilsLogInfo(true, "=refreshCameraUploadView=======================" + backupEmail); + if (!TextUtils.isEmpty(backupEmail)) { + try { + selectRepoConfig = databaseHelper.getRepoConfig(backupEmail); + } catch (Exception e) { + Utils.utilsLogInfo(true, "=refreshCameraUploadView=======================" + e.toString()); + } + + } + if (selectRepoConfig != null && !TextUtils.isEmpty(selectRepoConfig.getRepoName())) { + cBackupFolderRepo.setSummary(backupEmail + "/" + selectRepoConfig.getRepoName()); + } else { + cBackupFolderRepo.setSummary(getActivity().getString(R.string.folder_backup_select_repo_hint)); + } + + } else { + cFolderBackupCategory.removePreference(cBackupFolderMode); + cFolderBackupCategory.removePreference(cBackupFolderRepo); + cFolderBackupCategory.removePreference(cBackupFolderPref); + cFolderBackupCategory.removePreference(cBackupFolderState); + } if (cameraManager.isCameraUploadEnabled()) { cUploadCategory.addPreference(cUploadRepoPref); @@ -715,6 +853,26 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } else if (resultCode == Activity.RESULT_CANCELED) { + } + refreshCameraUploadView(); + break; + case CHOOSE_BACKUP_UPLOAD_REQUEST: + if (resultCode == Activity.RESULT_OK) { + if (data == null) { + return; + } + final boolean pathOn = data.getBooleanExtra(FolderBackupConfigActivity.BACKUP_SELECT_PATHS_SWITCH, false); + final ArrayList pathListExtra = data.getStringArrayListExtra(FolderBackupConfigActivity.BACKUP_SELECT_PATHS); + if (pathOn && pathListExtra != null) { + if (backupSelectPaths == null) { + backupSelectPaths = new ArrayList<>(); + } else { + backupSelectPaths.clear(); + } + backupSelectPaths.addAll(pathListExtra); + cBackupFolderPref.setSummary(pathListExtra.size() + ""); + } + } refreshCameraUploadView(); break; @@ -864,14 +1022,59 @@ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, Strin } }; + private void showWifiDialog() { + + String[] buckModes = {"WIFI", getActivity().getString(R.string.folder_backup_mode)}; + new AlertDialog.Builder(getActivity()) + .setCancelable(false) + .setTitle(getActivity().getString(R.string.folder_backup_mode_title)) + .setPositiveButton(getActivity().getString(R.string.ok), null) + .setSingleChoiceItems(buckModes, 0, new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialogInterface, int i) { + + Toast.makeText(getActivity(), buckModes[i], Toast.LENGTH_SHORT).show(); + if (i == 0) { + SettingsManager.instance().saveFolderBackupDataPlanAllowed(false); + } else { + SettingsManager.instance().saveFolderBackupDataPlanAllowed(true); + } + } + }) + .show(); + + } + + @Override + public void onStart() { + super.onStart(); + EventBus.getDefault().register(this); + } + + @Override + public void onStop() { + super.onStop(); + EventBus.getDefault().unregister(this); + } @Subscribe(threadMode = ThreadMode.MAIN) public void onEvent(CameraSyncEvent result) { + int scanUploadStatus = SeadroidApplication.getInstance().getScanUploadStatus(); + if (cameraManager.isCameraUploadEnabled() && scanUploadStatus > 0) { + if (scanUploadStatus == CameraSyncStatus.SCAN_END) { + SeadroidApplication.getInstance().setScanUploadStatus(CameraSyncStatus.NORMAL); + } + cBackupRepoState.setSummary(Utils.getUploadStateShow(getActivity())); + } - cUploadRepoState.setSummary(Utils.getUploadStateShow(getActivity())); + } - Log.d(DEBUG_TAG, "==========" + result.getLogInfo()); - Utils.utilsLogInfo(true,"==========" + result.getLogInfo()); + @Subscribe(threadMode = ThreadMode.MAIN) + public void onEvent(FolderBackupEvent result) { + int totalBackup = SeadroidApplication.getInstance().getTotalBackup(); + int waitingBackup = SeadroidApplication.getInstance().getWaitingBackup(); + cBackupFolderState.setSummary(getActivity().getString(R.string.uploaded) + " " + (totalBackup - waitingBackup) + " / " + totalBackup); } } diff --git a/app/src/main/java/com/seafile/seadroid2/util/CameraSyncStatus.java b/app/src/main/java/com/seafile/seadroid2/util/CameraSyncStatus.java index 1e8f9fab9..3d3f9038e 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/CameraSyncStatus.java +++ b/app/src/main/java/com/seafile/seadroid2/util/CameraSyncStatus.java @@ -1,6 +1,7 @@ package com.seafile.seadroid2.util; public class CameraSyncStatus { + public final static int NORMAL = 0; public final static int SCANNING = 1; public final static int NETWORK_UNAVAILABLE = 2; public final static int UPLOADING = 3; diff --git a/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java b/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java new file mode 100644 index 000000000..898c9243f --- /dev/null +++ b/app/src/main/java/com/seafile/seadroid2/util/CrashHandler.java @@ -0,0 +1,140 @@ +package com.seafile.seadroid2.util; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; +import android.util.Log; +import android.widget.Toast; + +import com.seafile.seadroid2.R; +import com.seafile.seadroid2.SeadroidApplication; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.lang.Thread.UncaughtExceptionHandler; +import java.lang.reflect.Field; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class CrashHandler implements UncaughtExceptionHandler { + public static final String TAG = "CrashHandler"; + private UncaughtExceptionHandler mDefaultHandler; + private static CrashHandler INSTANCE = new CrashHandler(); + private Context mContext; + private Map infos = new HashMap(); + private DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss"); + + private CrashHandler() { + } + + public static CrashHandler getInstance() { + return INSTANCE; + } + + public void init(Context context) { + mContext = context; + mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler(this); + } + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + if (myHandleException(ex) && mDefaultHandler != null) { + Utils.utilsLogInfo(false, "=============uncaughtException"); + Toast.makeText(mContext, mContext.getString(R.string.UncaughtExceptionHandler_err), Toast.LENGTH_SHORT).show(); + mDefaultHandler.uncaughtException(thread, ex); + } else { + Utils.utilsLogInfo(false, "===else==========uncaughtException"); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + Log.e(TAG, "error : ", e); + } + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(1); + } + } + + public boolean myHandleException(Throwable ex) { + if (ex == null) { + return false; + } + collectDeviceInfo(mContext); + saveCrashInfo2File(ex); + return true; + } + + public void collectDeviceInfo(Context ctx) { + try { + PackageManager pm = ctx.getPackageManager(); + PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES); + if (pi != null) { + String versionName = pi.versionName == null ? "null" : pi.versionName; + String versionCode = pi.versionCode + ""; + infos.put("versionName", versionName); + infos.put("versionCode", versionCode); + infos.put("phoneModelInfo", "phoneModelInfo-------" + SeafileLog.getDeviceBrand() + "/" + SeafileLog.getSystemModel() + "/" + SeafileLog.getSystemVersion()); + } + } catch (NameNotFoundException e) { + Log.e(TAG, "an error occured when collect package info", e); + } + Field[] fields = Build.class.getDeclaredFields(); + for (Field field : fields) { + try { + field.setAccessible(true); + infos.put(field.getName(), field.get(null).toString()); + } catch (Exception e) { + Log.e(TAG, "an error occured when collect crash info", e); + } + } + } + + private String saveCrashInfo2File(Throwable ex) { + + StringBuffer sb = new StringBuffer(); + for (Map.Entry entry : infos.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + sb.append(key + "=" + value + "\n"); + } + + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + ex.printStackTrace(printWriter); + Throwable cause = ex.getCause(); + while (cause != null) { + cause.printStackTrace(printWriter); + cause = cause.getCause(); + } + printWriter.close(); + String result = writer.toString(); + sb.append(result); + try { + long timestamp = System.currentTimeMillis(); + String time = formatter.format(new Date()); + String fileName = Utils.EXCEPTION_TYPE_CRASH + time + "-" + timestamp + ".log"; + + File[] externalMediaDirs = SeadroidApplication.getAppContext().getExternalMediaDirs(); + String rootPath = externalMediaDirs[0].getAbsolutePath(); + File dirsFile = new File(rootPath + "/Seafile/"); + if (!dirsFile.exists()) { + dirsFile.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(rootPath + "/Seafile/" + fileName); + fos.write(sb.toString().getBytes()); + fos.close(); + return fileName; + } catch (Exception e) { + Log.e(TAG, "an error occured while writing file...", e); + } + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/seafile/seadroid2/util/Utils.java b/app/src/main/java/com/seafile/seadroid2/util/Utils.java index a14fd52f7..e79a6ae52 100644 --- a/app/src/main/java/com/seafile/seadroid2/util/Utils.java +++ b/app/src/main/java/com/seafile/seadroid2/util/Utils.java @@ -89,6 +89,8 @@ public class Utils { // public static final String NOGROUP = "$nogroup"; public static final String PERSONAL_REPO = "personal_repo"; public static final String SHARED_REPO = "shared_repo"; + public static final String TRANSFER_PHOTO_TAG = "camera_upload"; + public static final String TRANSFER_FOLDER_TAG = "folder_backup"; private static final String DEBUG_TAG = "Utils"; private static final String HIDDEN_PREFIX = "."; private static HashMap suffixIconMap = null; @@ -300,6 +302,23 @@ public static int getFileIcon(String name) { return getResIdforMimetype(mime); } + public static int getFileIconSuffix(String suffix) { + if (suffix.length() == 0) { + return R.drawable.file; + } + + HashMap map = getSuffixIconMap(); + Integer i = map.get(suffix); + if (i != null) + return i; + + if (suffix.equals("flv")) { + return R.drawable.file_video; + } + String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(suffix); + return getResIdforMimetype(mime); + } + public static boolean isViewableImage(String name) { String suffix = name.substring(name.lastIndexOf('.') + 1).toLowerCase(); if (suffix.length() == 0) @@ -486,6 +505,9 @@ public static String getFileMimeType(File file) { } public static void copyFile(File src, File dst) throws IOException { + if (src == null || dst == null) { + return; + } InputStream in = new BufferedInputStream(new FileInputStream(src)); OutputStream out = new BufferedOutputStream(new FileOutputStream(dst)); @@ -994,5 +1016,6 @@ public static void utilsLogInfo(boolean b, String info) { Log.d(DEBUG_TAG, info); } } + public static final String EXCEPTION_TYPE_CRASH = "crash_exception"; } diff --git a/app/src/main/res/drawable-xhdpi/ic_tabbar_partition.png b/app/src/main/res/drawable-xhdpi/ic_tabbar_partition.png new file mode 100644 index 000000000..93dec392d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_tabbar_partition.png differ diff --git a/app/src/main/res/drawable/bottom_line.xml b/app/src/main/res/drawable/bottom_line.xml new file mode 100644 index 000000000..a0e88ca37 --- /dev/null +++ b/app/src/main/res/drawable/bottom_line.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bg_settings_section_folder_backup.xml b/app/src/main/res/layout/bg_settings_section_folder_backup.xml new file mode 100644 index 000000000..7b2291262 --- /dev/null +++ b/app/src/main/res/layout/bg_settings_section_folder_backup.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/cuc_local_directory_fragment.xml b/app/src/main/res/layout/cuc_local_directory_fragment.xml index 70124d727..77271671f 100644 --- a/app/src/main/res/layout/cuc_local_directory_fragment.xml +++ b/app/src/main/res/layout/cuc_local_directory_fragment.xml @@ -40,20 +40,24 @@ android:textSize="@dimen/tv_subtitle_txt_size"/> - - - +