diff --git a/transliteration.admin.inc b/transliteration.admin.inc
index f1f5f3d..2c13f43 100644
--- a/transliteration.admin.inc
+++ b/transliteration.admin.inc
@@ -54,82 +54,27 @@ function transliteration_retroactive() {
  * Form submit function; retroactively transliterates existing file names.
  *
  * @see transliteration_retroactive()
+ * @see transliteration_retroactive_batch_operation_callback()
+ * @see transliteration_retroactive_batch_finished()
  */
 function transliteration_retroactive_submit($form, &$form_state) {
-  $count = 0;
-  $errors = array();
-  $result = transliteration_file_query()->execute();
-
-  while ($file = $result->fetchObject()) {
-    $wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);
-    $scheme = file_uri_scheme($file->uri);
-
-    // Missing implementation.
-    if (!$wrapper) {
-      $errors[] = file_uri_target($file->uri);
-      continue;
-    }
-
-    // Skip non-writable stream wrappers.
-    $writeable_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE);
-    if (!isset($writeable_stream_wrappers[$scheme])) {
-      continue;
-    }
-
-    // Missing file.
-    if (!file_exists($wrapper->realpath())) {
-      $errors[] = file_uri_target($file->uri);
-      continue;
-    }
-
-    // Sanitize file name.
-    $filename = transliteration_clean_filename(basename($file->uri));
-    // Build destination URI.
-    $destination = file_stream_wrapper_uri_normalize(drupal_dirname($file->uri) . '/' . $filename);
-    $destination = file_destination($destination, FILE_EXISTS_RENAME);
-
-    // Rename and update the file record accordingly.
-    if ($wrapper->rename($file->uri, $destination)) {
-      // If possible, add a url redirect to handle old URL references.
-      if (module_exists('redirect')) {
-        $redirect = new stdClass();
-        redirect_object_prepare($redirect, array(
-          'source' => $wrapper->getDirectoryPath() . '/' . file_uri_target($file->uri),
-          'redirect' => $wrapper->getDirectoryPath() . '/' . file_uri_target($destination),
-          'status_code' => 301)
-        );
-        redirect_save($redirect);
-      }
-      $file->uri = $destination;
-      // Don't use file_save() as it modifies the timestamp.
-      drupal_write_record('file_managed', $file, 'fid');
-      // Inform modules that the file has been updated.
-      module_invoke_all('file_update', $file);
-      $count++;
-    }
-    else {
-      $errors[] = file_uri_target($file->uri);
-    }
-  }
-
-  if ($errors) {
-    $message = t('Not all file names could be converted. The following files could not be accessed and have been ignored:');
-    $message .= theme('item_list', array('items' => $errors));
-    drupal_set_message($message, 'error');
-  }
-  else {
-    drupal_set_message(t('@filenames have been successfully transliterated.', array('@filenames' => format_plural($count, '1 file name', '@count file names'))));
-  }
-
-  // Flush page cache.
-  cache_clear_all();
+  // We set up a patch process for updating the files.
+  $batch = array(
+    'operations' => array(
+      array('transliteration_retroactive_batch_operation_callback', array()),
+    ),
+    'finished' => 'transliteration_retroactive_batch_finished',
+    'title' => t('Retroactively transliterating existing file names'),
+    'file' => drupal_get_path('module', 'transliteration') . '/transliteration.admin.inc',
+  );
+  batch_set($batch);
 }
 
 /**
  * Builds a query that returns all file names from the database containing non-ASCII characters.
  *
- * @return
- *   SelectQuery
+ * @return SelectQuery
+ *   A base query for getting all files that should be transliterated.
  */
 function transliteration_file_query() {
   // Regular expressions are not supported by Drupal's database layer and
@@ -160,5 +105,145 @@ function transliteration_file_query() {
 
   return db_select('file_managed')
     ->fields('file_managed')
-    ->condition('uri', $regex, $operator);
+    ->condition('uri', $regex, $operator)
+    ->orderBy('fid', 'ASC');
+}
+
+/**
+ * Batch API operation callback for updating files.
+ *
+ * @param $context
+ */
+function transliteration_retroactive_batch_operation_callback(&$context) {
+
+  if (!isset($context['sandbox']['progress'])) {
+    $context['sandbox']['progress'] = 0;
+    $context['sandbox']['last_fid'] = 0;
+    $context['sandbox']['max'] = transliteration_file_query()->countQuery()->execute()->fetchColumn();
+    $context['sandbox']['file_errors'] = array();
+    $context['results'] = array(
+      'count' => 0,
+      'errors' => array(),
+    );
+  }
+
+  // We only process ten files at a time.
+  $limit = 10;
+
+  // We get the next several files, starting from the last processed file.
+  $result = transliteration_file_query()
+    ->condition('fid', $context['sandbox']['last_fid'], '>')
+    ->range(0, $limit) // We do not need an offset, as we use last_fid.
+    ->execute();
+
+  while ($file = $result->fetchObject()) {
+    // If something went wrong, we log the file to an errors array.
+    if (!_transliteration_retroactive_update_single_file($file)) {
+      $context['results']['errors'][] = file_uri_target($file->uri);
+    }
+    else {
+      $context['results']['count']++;
+    }
+
+    $context['sandbox']['progress']++;
+    $context['sandbox']['last_fid'] = $file->fid;
+  }
+
+  if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
+    $context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
+  }
+
+}
+
+/**
+ * Helper function to update a single file.
+ *
+ * @param $file
+ *   File object from the database query.
+ *
+ * @return bool
+ *   Returns TRUE if the update was succesful.
+ */
+function _transliteration_retroactive_update_single_file($file) {
+  $wrapper = file_stream_wrapper_get_instance_by_uri($file->uri);
+  $scheme = file_uri_scheme($file->uri);
+
+  // Missing implementation.
+  if (!$wrapper) {
+    return FALSE;
+  }
+
+  // Skip non-writable stream wrappers.
+  $writeable_stream_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_WRITE);
+  if (!isset($writeable_stream_wrappers[$scheme])) {
+    return FALSE;
+  }
+
+  // Missing file.
+  if (!file_exists($wrapper->realpath())) {
+    return FALSE;
+  }
+
+  // Sanitize file name.
+  $filename = transliteration_clean_filename(basename($file->uri));
+  // Build destination URI.
+  $destination = file_stream_wrapper_uri_normalize(drupal_dirname($file->uri) . '/' . $filename);
+  $destination = file_destination($destination, FILE_EXISTS_RENAME);
+
+  // Rename and update the file record accordingly.
+  if ($wrapper->rename($file->uri, $destination)) {
+    // If possible, add a url redirect to handle old URL references.
+    if (module_exists('redirect')) {
+      $redirect = new stdClass();
+      redirect_object_prepare($redirect, array(
+          'source' => $wrapper->getDirectoryPath() . '/' . file_uri_target($file->uri),
+          'redirect' => $wrapper->getDirectoryPath() . '/' . file_uri_target($destination),
+          'status_code' => 301)
+      );
+      redirect_save($redirect);
+    }
+    $file->uri = $destination;
+    // Don't use file_save() as it modifies the timestamp.
+    drupal_write_record('file_managed', $file, 'fid');
+    // Inform modules that the file has been updated.
+    module_invoke_all('file_update', $file);
+
+    return TRUE;
+  }
+  else {
+    return FALSE;
+  }
+}
+
+/**
+ * Finished callback for the transliteration update.
+ *
+ * @param $success
+ * @param $result
+ * @param $operations
+ *
+ * @see https://www.drupal.org/node/180528
+ */
+function transliteration_retroactive_batch_finished($success, $result, $operations) {
+
+  if ($success) {
+    if (count($result['errors'])) {
+      $message = t('Not all file names could be converted. The following files could not be accessed and have been ignored:');
+      $message .= theme('item_list', array('items' => $result['errors']));
+      drupal_set_message($message, 'error');
+    }
+    else {
+      drupal_set_message(t('@filenames have been successfully transliterated.', array('@filenames' => format_plural($result['count'], '1 file name', '@count file names'))));
+    }
+  }
+  else {
+    // An error occurred.
+    // $operations contains the operations that remained unprocessed.
+    $error_operation = reset($operations);
+    $message = t('An error occurred while processing %error_operation with arguments: @arguments', array('%error_operation' => $error_operation[0], '@arguments' => print_r($error_operation[1], TRUE)));
+    drupal_set_message($message, 'error');
+  }
+
+  // Flush page cache.
+  cache_clear_all();
 }
