Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import org.junit.Test

@Suppress("TooManyFunctions")
class FileNameValidatorTests : AbstractOnServerIT() {
companion object {
private const val MAX_FILENAME_SIZE = 250
}

private var capability: OCCapability = fileDataStorageManager.getCapability(account.name)

Expand Down Expand Up @@ -109,6 +112,20 @@ class FileNameValidatorTests : AbstractOnServerIT() {
assertEquals(targetContext.getString(R.string.filename_empty), result)
}

@Test
fun testLongFileName() {
val filename = "a".repeat(MAX_FILENAME_SIZE)
val result = FileNameValidator.checkFileName(filename, capability, targetContext)
assertNull(result)
}

@Test
fun testTooLongFileName() {
val filename = "a".repeat(MAX_FILENAME_SIZE + 1)
val result = FileNameValidator.checkFileName(filename, capability, targetContext)
assertEquals(targetContext.getString(R.string.filename_too_long), result)
}

@Test
fun testFileAlreadyExists() {
val existingFiles = setOf("existingFile")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.resources.status.OCCapability

object FileNameValidator {
// As defined, server-side, in FilenameValidator::validateFilename
private const val MAX_FILENAME_SIZE = 250

/**
* Checks the validity of a file name.
Expand All @@ -45,6 +47,10 @@ object FileNameValidator {
return context.getString(R.string.filename_empty)
}

if (filename.length > MAX_FILENAME_SIZE) {
return context.getString(R.string.filename_too_long)
}

if (isFileNameAlreadyExist(filename, existingFileNames)) {
return context.getString(R.string.file_already_exists)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import androidx.annotation.WorkerThread
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.button.MaterialButton
import com.google.android.material.dialog.MaterialAlertDialogBuilder
Expand All @@ -31,10 +33,10 @@ import com.nextcloud.client.account.User
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.network.ClientFactory
import com.nextcloud.client.network.ClientFactory.CreationException
import com.nextcloud.utils.ResultParser.data
import com.nextcloud.utils.SnackbarUtil
import com.nextcloud.utils.extensions.getParcelableArgument
import com.nextcloud.utils.fileNameValidator.FileNameValidator
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.databinding.ChooseTemplateBinding
import com.owncloud.android.datamodel.FileDataStorageManager
Expand All @@ -53,6 +55,9 @@ import com.owncloud.android.ui.adapter.TemplateAdapter
import com.owncloud.android.utils.FileStorageUtils
import com.owncloud.android.utils.KeyboardUtils
import com.owncloud.android.utils.theme.ViewThemeUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.lang.ref.WeakReference
import javax.inject.Inject

Expand Down Expand Up @@ -195,7 +200,62 @@ class ChooseTemplateDialogFragment :
}

private fun createFromTemplate(template: Template, path: String) {
CreateFileFromTemplateTask(this, clientFactory, currentAccount.user, template, path, creator).execute()
lifecycleScope.launch {
val result = withContext(Dispatchers.IO) { createFileOnServer(template, path) }

val binding = _binding
if (!isAdded || binding == null) {
Log_OC.w(TAG, "Dialog no longer attached, ignoring create from template result")
return@launch
}

when (result) {
is CreateFileFromTemplateResult.Success -> openEditor(result)
else -> result.showError(binding.list)
}
}
}

@WorkerThread
private fun createFileOnServer(template: Template, path: String): CreateFileFromTemplateResult {
return try {
val user = currentAccount.user

val createResult = DirectEditingCreateFileRemoteOperation(path, creator?.editor, creator?.id, template.id)
.execute(clientFactory.createNextcloudClient(user))
val editorUrl = createResult.resultData?.takeIf { createResult.isSuccess }
?: return CreateFileFromTemplateResult.FailedCreateFile

readAndStoreCreatedFile(user, path, editorUrl)
} catch (e: CreationException) {
Log_OC.e(TAG, "Error creating file from template!", e)
CreateFileFromTemplateResult.FailedCreateFromTemplate
}
}

@WorkerThread
private fun readAndStoreCreatedFile(user: User, path: String, editorUrl: String): CreateFileFromTemplateResult {
val remoteFile = ReadFileRemoteOperation(path)
.execute(clientFactory.create(user))
.data(RemoteFile::class.java)
?: return CreateFileFromTemplateResult.FailedReadFile

fileDataStorageManager.saveFile(FileStorageUtils.fillOCFile(remoteFile))
return fileDataStorageManager.getFileByPath(path)
?.let { CreateFileFromTemplateResult.Success(editorUrl, it) }
?: CreateFileFromTemplateResult.FailedSaveLocalFile
}

private fun openEditor(result: CreateFileFromTemplateResult.Success) {
val editorWebView = Intent(requireContext(), TextEditorWebView::class.java).apply {
putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text")
putExtra(ExternalSiteWebView.EXTRA_URL, result.url)
putExtra(FileActivity.EXTRA_FILE, result.file)
putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false)
}

startActivity(editorWebView)
dismiss()
}

@SuppressLint("NotifyDataSetChanged")
Expand Down Expand Up @@ -282,80 +342,25 @@ class ChooseTemplateDialogFragment :
binding.filenameContainer.error = state.errorMessage
}

@Suppress("LongParameterList", "DEPRECATION")
private class CreateFileFromTemplateTask(
chooseTemplateDialogFragment: ChooseTemplateDialogFragment,
private val clientFactory: ClientFactory?,
private val user: User,
private val template: Template,
private val path: String,
private val creator: Creator?
) : AsyncTask<Void, Void, String>() {
private val chooseTemplateDialogFragmentWeakReference: WeakReference<ChooseTemplateDialogFragment> =
WeakReference(chooseTemplateDialogFragment)
private var file: OCFile? = null

@Deprecated("Deprecated in Java")
@Suppress("ReturnCount") // legacy code
override fun doInBackground(vararg params: Void): String {
return try {
val client = clientFactory?.create(user) ?: return ""
val nextcloudClient = clientFactory.createNextcloudClient(user)
val result = DirectEditingCreateFileRemoteOperation(
path,
creator?.editor,
creator?.id,
template.id
).execute(nextcloudClient)
if (!result.isSuccess) {
return ""
}
val newFileResult = ReadFileRemoteOperation(path).execute(client)
if (!newFileResult.isSuccess) {
return ""
}
val fragment = chooseTemplateDialogFragmentWeakReference.get() ?: return ""
val context = fragment.context
?: // fragment has been detached
return ""
val storageManager = FileDataStorageManager(
user,
context.contentResolver
)
val temp = FileStorageUtils.fillOCFile(newFileResult.data[0] as RemoteFile)
storageManager.saveFile(temp)
file = storageManager.getFileByPath(path)
result.resultData
} catch (e: CreationException) {
Log_OC.e(TAG, "Error creating file from template!", e)
""
}
}
sealed class CreateFileFromTemplateResult {
data class Success(val url: String, val file: OCFile) : CreateFileFromTemplateResult()

@Deprecated("Deprecated in Java")
override fun onPostExecute(url: String) {
val fragment = chooseTemplateDialogFragmentWeakReference.get()
if (fragment == null || !fragment.isAdded) {
Log_OC.e(TAG, "Error creating file from template!")
return
}
data object FailedCreateFile : CreateFileFromTemplateResult()
data object FailedCreateFromTemplate : CreateFileFromTemplateResult()
data object FailedReadFile : CreateFileFromTemplateResult()
data object FailedSaveLocalFile : CreateFileFromTemplateResult()

if (url.isEmpty()) {
SnackbarUtil.show(fragment.binding.list, R.string.error_creating_file_from_template)
return
}
fun showError(view: View) {
if (this is Success) return

val editorWebView = Intent(MainApp.getAppContext(), TextEditorWebView::class.java).apply {
putExtra(ExternalSiteWebView.EXTRA_TITLE, "Text")
putExtra(ExternalSiteWebView.EXTRA_URL, url)
putExtra(FileActivity.EXTRA_FILE, file)
putExtra(ExternalSiteWebView.EXTRA_SHOW_SIDEBAR, false)
val messageId = when (this) {
FailedCreateFile -> R.string.error_creating_file
FailedCreateFromTemplate -> R.string.error_creating_file_from_template
FailedReadFile -> R.string.error_reading_file
FailedSaveLocalFile -> R.string.error_saving_local_file
}

fragment.run {
startActivity(editorWebView)
dismiss()
}
SnackbarUtil.show(view, messageId)
}
}

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@
<string name="filename_forbidden_characters">Forbidden characters: / \\ &lt; &gt; : " | ? *</string>
<string name="filename_forbidden_charaters_from_server">Filename contains at least one invalid character</string>
<string name="filename_empty">Filename cannot be empty</string>
<string name="filename_too_long">Filename cannot be longer than 250 characters</string>
<string name="folder_name_empty">Folder name cannot be empty</string>
<string name="label_empty">Label cannot be empty</string>
<string name="wait_a_moment">Wait a moment…</string>
Expand Down Expand Up @@ -1389,7 +1390,10 @@
<string name="file_list_empty_unified_search_start_search_description">Type in the search bar above to find files, contacts, calendar events, and more across your account.</string>

<string name="file_list_empty_gallery">Found no images or videos</string>
<string name="error_creating_file">Error creating file</string>
<string name="error_creating_file_from_template">Error creating file from template</string>
<string name="error_reading_file">Error reading file</string>
<string name="error_saving_local_file">Error saving local file</string>
<string name="no_send_app">No app available for sending the selected files</string>
<string name="storage_permission_all_files_access">All files access</string>
<string name="storage_permission_media_read_only">Media read-only</string>
Expand Down
Loading