public function run() { Rails::systemExit()->register(function () { if (!$this->finished) { $this->active = false; $this->data->success = false; $this->data->error = "Couldn't finish successfuly"; $this->save(); } }); # Ugly: set the current user ID to the one set in the batch, so history entries # will be created as that user. // $old_thread_user = Thread::current["danbooru-user"]; // $old_thread_user_id = Thread::current["danbooru-user_id"]; // $old_ip_addr = Thread::current["danbooru-ip_addr"]; // Thread::current["danbooru-user"] = User::find_by_id(self.user_id) // Thread::current["danbooru-user_id"] = $this->user_id // Thread::current["danbooru-ip_addr"] = $this->ip $this->active = true; $this->save(); $post = Post::create(['source' => $this->url, 'tags' => $this->tags, 'updater_user_id' => $this->user_id, 'updater_ip_addr' => $this->ip, 'user_id' => $this->user_id, 'ip_addr' => $this->ip, 'status' => "active"]); if ($post->errors()->blank()) { if (CONFIG()->dupe_check_on_upload && $post->image() && !$post->parent_id) { $options = ['services' => SimilarImages::get_services("local"), 'type' => 'post', 'source' => $post]; $res = SimilarImages::similar_images($options); if (!empty($res['posts'])) { $post->tags = $post->tags() . " possible_duplicate"; $post->save(); } } $this->data->success = true; $this->data->post_id = $post->id; } elseif ($post->errors()->on('md5')) { // $p = $post->errors(); $p = Post::where(['md5' => $post->md5])->first(); $this->data->success = false; $this->data->error = "Post already exists"; $this->data->post_id = $p->id; } else { // p $post.errors $this->data->success = false; $this->data->error = $post->errors()->fullMessages(", "); } if ($this->data->success) { $this->status = 'finished'; } else { $this->status = 'error'; } $this->active = false; $this->save(); $this->finished = true; // Thread::current["danbooru-user"] = old_thread_user // Thread::current["danbooru-user_id"] = old_thread_user_id // Thread::current["danbooru-ip_addr"] = old_ip_addr }
public function execute_external_data_search() { # current_user will be needed to save post history. # Set the first admin as current user. User::set_current_user(User::where('level = ?', CONFIG()->user_levels['Admin'])->first()); if (empty($this->data->last_post_id)) { $this->data->last_post_id = 0; } $post_id = $this->data->last_post_id + 1; $config = array_merge(['servers' => [], 'interval' => 3, 'source' => true, 'merge_tags' => true, 'limit' => 100, 'set_rating' => false, 'exclude_tags' => [], 'similarity' => 90], CONFIG()->external_data_search_config); $limit = $config['limit']; $interval = $config['interval']; $search_options = ['type' => 'post', 'data_search' => true, 'services' => $config['servers'], 'threshold' => $config['similarity']]; $post_count = !$limit ? -1 : 0; while ($post_count < $limit) { if (!($post = Post::where('id >= ? AND status != "deleted"', $post_id)->order('id ASC')->first())) { break; } $search_options['source'] = $post; $new_tags = []; $source = null; $external_posts = SimilarImages::similar_images($search_options)['posts_external']; $rating_set = false; foreach ($external_posts as $ep) { if (!$rating_set && $config['set_rating'] && $ep->rating) { $post->rating = $ep->rating; $rating_set = true; } if ($config['source'] && !$source && $ep->source) { $source = $ep->source; } $new_tags = array_merge($new_tags, explode(' ', $ep->tags)); } # Exclude tags. $new_tags = array_diff($new_tags, $config['exclude_tags']); if ($config['merge_tags']) { $new_tags = array_merge($new_tags, $post->tags); } $new_tags = array_filter(array_unique($new_tags)); $post->new_tags = $new_tags; if ($source) { } $post->source = $source; $post->save(); if ($limit) { $post_count++; } $this->update_data(['last_post_id' => $post->id]); $post_id = $post->id + 1; if ($config['interval']) { sleep($config['interval']); } } }
public function similar() { $params = array_merge(['file' => null, 'url' => null, 'id' => $this->params()->id, 'search_id' => null, 'services' => null, 'threshold' => null, 'forcegray' => null, 'initial' => null, 'width' => null, 'height' => null], $this->params()->toArray()); if (!empty($params['data_search']) && !current_user()->is_mod_or_higher()) { unset($params['data_search']); } if (!SimilarImages::valid_saved_search($params['search_id'])) { $params['search_id'] = null; } if (!empty($params['width'])) { $params['width'] = (int) $params['width']; } if (!empty($params['height'])) { $params['height'] = (int) $params['height']; } $this->initial = $params['initial']; if ($this->initial && !$params['services']) { $params['services'] = "local"; } $this->services = SimilarImages::get_services($params['services']); if ($this->params()->id) { $this->compared_post = Post::find($this->params()->id); } else { $this->compared_post = new Post(); } $this->errors = null; $this->posts = Post::emptyCollection(); $this->similar = []; $similarity = []; if ($this->compared_post && $this->compared_post->is_deleted()) { $this->respond_to_error("Post deleted", ['post#show', 'id' => $this->params()->id, 'tag_title' => $this->compared_post->tag_title()]); return; } # We can do these kinds of searches: # # File: Search from a specified file. The image is saved locally with an ID, and sent # as a file to the search servers. # # URL: search from a remote URL. The URL is downloaded, and then treated as a :file # search. This way, changing options doesn't repeatedly download the remote image, # and it removes a layer of abstraction when an error happens during download # compared to having the search server download it. # # Post ID: Search from a post ID. The preview image is sent as a URL. # # Search ID: Search using an image uploaded with a previous File search, using # the search MD5 created. We're not allowed to repopulate filename fields in the # user's browser, so we can't re-submit the form as a file search when changing search # parameters. Instead, we hide the search ID in the form, and use it to recall the # file from before. These files are expired after a while; we check for expired files # when doing later searches, so we don't need a cron job. $search = function (array $params) { $options = array_merge($params, ['services' => $this->services]); # Check search_id first, so options links that include it will use it. If the # user searches with the actual form, search_id will be cleared on submission. if (!empty($params['search_id'])) { $file_path = SimilarImages::find_saved_search($params['search_id']); if (!$file_path) { # The file was probably purged. Delete :search_id before redirecting, so the # error doesn't loop. // unset($params.delete(:search_id) return ['errors' => ['error' => "Search expired"]]; } } elseif (!empty($params['url']) || !empty($_FILES['file']['tmp_name'])) { # Save the file locally. try { if (!empty($params['url'])) { $file = Danbooru::http_get_streaming($params['url']); $search = SimilarImages::save_search($file); } else { # file $file = file_get_contents($_FILES['file']['tmp_name']); $search = SimilarImages::save_search($file); $options['type'] = 'file'; } } catch (Moebooru\Exception\ResizeErrorException $e) { return ['errors' => ['error' => $e->getMessage()]]; } catch (Danbooru\Exception\ExceptionInterface $e) { return ['errors' => ['error' => $e->getMessage()]]; } // } rescue Timeout::Error => e // return { :errors => { :error => "Download timed out" } } // end $file_path = $search['file_path']; # Set :search_id in params for generated URLs that point back here. $params['search_id'] = $search['search_id']; # The :width and :height params specify the size of the original image, for display # in the results. The user can specify them; if not specified, fill it in. empty($params['width']) && ($params['width'] = $search['original_width']); empty($params['height']) && ($params['height'] = $search['original_height']); } elseif (!empty($params['id'])) { $options['source'] = $this->compared_post; $options['type'] = 'post'; } if (!empty($params['search_id'])) { $options['source'] = $file_path; $options['source_filename'] = $params['search_id']; $options['source_thumb'] = "/data/search/" . $params['search_id']; $options['type'] = 'file'; } $options['width'] = $params['width']; $options['height'] = $params['height']; if ($options['type'] == 'file') { SimilarImages::cull_old_searches(); } return SimilarImages::similar_images($options); }; $this->searched = false; if ($this->params()->url || $this->params()->id || !empty($_FILES['file']) && empty($_FILES['file']['error']) || !empty($this->params()->search_id)) { $res = $search($params); # Error when no service was selected and/or local search isn't supported if (is_string($res['errors'])) { $this->notice($res['errors']); } else { $this->errors = !empty($res['errors']) ? $res['errors'] : []; $this->searched = true; $this->search_id = $this->params()->search_id; # Never pass :file on through generated URLs. $params['file'] = null; } } else { $res = []; $this->errors = []; } if ($res && $this->searched) { !empty($res['posts']) && ($this->posts = $res['posts']); $this->similar = $res; !empty($res['similarity']) && ($similarity = $res['similarity']); } if ($this->request()->format() == "json" || $this->request()->format() == "xml") { if (!empty($this->errors['error'])) { $this->respond_to_error($this->errors['error'], ['#index'], ['status' => 503]); return; } if (!$this->searched) { $this->respond_to_error("no search supplied", ['#index'], ['status' => 503]); return; } } $this->respondTo(['html' => function () use($similarity, $res) { if ($this->initial && !$this->posts->any()) { // flash.keep $this->redirectTo(['post#show', 'id' => $this->params()->id, 'tag_title' => $this->compared_post->tag_title()]); return; } if (!empty($this->errors['error'])) { $this->notice($this->errors['error']); } if ($this->searched) { !empty($res['posts_external']) && $this->posts->merge($res['posts_external']); $this->posts->sort(function ($a, $b) use($similarity) { $aid = spl_object_hash($a); $bid = spl_object_hash($b); if ($similarity[$aid] == $similarity[$bid]) { return 0; } elseif ($similarity[$aid] > $similarity[$bid]) { return 1; } return -1; }); # Add the original post to the start of the list. if (!empty($res['source'])) { $this->posts[] = $res['source']; } elseif (!empty($res['external_source'])) { $this->posts[] = $res['external_source']; } } }, 'json' => function () use($res) { foreach ($this->posts as $post) { $post->similarity = $res['similarity'][spl_object_hash($post)]; } if (!empty($res['posts_external'])) { foreach ($res['posts_external'] as $post) { $post->similarity = $res['similarity'][spl_object_hash($post)]; } $this->posts->merge($res['posts_external']); } $api_data = ['posts' => $this->posts, 'search_id' => $this->params()->search_id]; if (!empty($res['source'])) { $api_data['source'] = $res['source']; } elseif (!empty($res['external_source'])) { $api_data['source'] = $res['external_source']; } else { $api_data['source'] = ''; } if (!empty($res['errors'])) { $api_data['error'] = []; foreach ($res['errors'] as $server => $error) { $services = !empty($error['services']) ? implode(', ', $error['services']) : array(); $api_data['error'][] = ['server' => $server, 'message' => $error['message'], 'services' => $services]; } } $this->respond_to_success('', [], ['api' => $api_data]); }]); $this->params = $params; }