📜 ⬆️ ⬇️

Writing Anime Wallpaper Downloader on MacRuby

Looking through theotaku.com in search of interesting wallpaper for the desktop, I caught myself thinking that it would be nice to write software that would automatically automatically download the wallpaper according to the tags instead of me. Based on the fact that I use Mac OS X as the main operating system, the software should also be for this platform and it is desirable to have a Cocoa interface. For some reason, I did not want to write all this in Java. Of course, there were many alternatives, but for some reason I wanted to try something different and at the same time learn something new. Immediately I remembered MacRuby and its tight integration with Cocoa . Armed with this idea, I immediately went to http://www.macruby.org/ and downloaded the latest stable version 0.10. After installation, I launched my favorite Xcode and created a new project called AnimeWallpaperDownloader

Our project on MacRuby consists of several files that XCode creates for us. The first file is main.m which simply runs the MacRuby script rb_main.rb
#import <Cocoa/Cocoa.h> #import <MacRuby/MacRuby.h> int main(int argc, char *argv[]) { return macruby_main("rb_main.rb", argc, argv); } 

rb_main.rb is a fairly simple script, it loads all other Ruby scripts and runs
NSApplicationMain
 framework 'Cocoa' # Loading all the Ruby project files. main = File.basename(__FILE__, File.extname(__FILE__)) dir_path = NSBundle.mainBundle.resourcePath.fileSystemRepresentation Dir.glob(File.join(dir_path, '*.{rb,rbo}')).map { |x| File.basename(x, File.extname(x)) }.uniq.each do |path| if path != main require(path) end end # Starting the Cocoa main loop. NSApplicationMain(0, nil) 

The last file that has already been created for us is AppDelegate.rb , which plays the role of NSApplicationDelegate . It contains an empty applicationDidFinishLaunching method which is called when our program has finished running.
 class AppDelegate attr_accessor :window def applicationDidFinishLaunching(a_notification) # Insert code here to initialize your application end end 

Here attr_accessor: window plays the role of IBOutlet * window and is already tied to the window of our program

Open MainMenu.xib and create a simple interface for our wallpaper downloader.


Next, add methods and outlets to our AppDelegate
 class AppDelegate attr_accessor :window attr_accessor :tags attr_accessor :size attr_accessor :number attr_accessor :saveInto attr_accessor :startButton attr_accessor :output attr_accessor :downprogress attr_accessor :downloader attr_accessor :img def applicationDidFinishLaunching(a_notification) @startButton.setEnabled(false) @downprogress.setStringValue('') @output.setStringValue('') @saveInto.stringValue = NSHomeDirectory()+"/Pictures" end def windowWillClose(a_notification) NSApp.terminate(self) end def controlTextDidChange(notification) sender = notification.object if sender == tags @startButton.setEnabled(@tags.stringValue.size > 0) elsif sender == number begin @number.setIntValue(@number.intValue) if @number.intValue < 0 @number.setIntValue(-@number.intValue) elsif @number.intValue == 0 @number.setIntValue(20) end rescue @number.setIntValue(20) end end end def browse(sender) dialog = NSOpenPanel.openPanel dialog.canChooseFiles = false dialog.canChooseDirectories = true dialog.allowsMultipleSelection = false if dialog.runModalForDirectory(nil, file:nil) == NSOKButton @saveInto.stringValue = dialog.filenames.first end end def startStop(sender) if @downloader == nil @downloader = Downloader.new(@tags.stringValue,@size.selectedItem.title,@number.stringValue,@saveInto.stringValue,self) @downloader.start @startButton.setTitle("Stop Download") else @downloader.stop @downloader = nil @startButton.setTitle("Start Download") end end def changeImage(file) @img.setImage(NSImage.alloc.initByReferencingFile(file)) end def clearStatus @downprogress.setStringValue('') end def setStatus(i,m) @downprogress.setStringValue("Downloading "+i.to_s()+" of "+m.to_s()) end def setStatusEnd(i) @downprogress.setStringValue("Downloaded "+i.to_s()+" wallpapers") end def puts(val) $stdout.puts val @output.setStringValue(val) end def stopped @startButton.setTitle("Start Download") down = @downloader @downloader = nil down.stop end end 

It's all pretty simple. The windowWillClose and controlTextDidChange methods are simply delegate methods for the program window and the first text field (until you enter the tag you cannot download anything).
The browse method opens a dialog box for selecting the directory where we save our wallpapers, we link it to the Browse button. The startStop method starts a jump, so we bind it to the Start Download button. The rest of the methods are auxiliary and will be used by the class Downloader , which we will use to find links and download wallpapers.
')
 require 'thread' require 'net/http' class Downloader attr_accessor :tags, :size, :number, :saveTo, :thread attr_accessor :app, :exit def initialize(tags, size, number, saveTo, app) @tags = tags.sub(' ','_') @size = size == 'Any' ? '' : size.sub('x','_') @number = number @saveTo = saveTo @app = app @exit = false end def getIndexPage(page) walls = {} url = 'http://www.theotaku.com/wallpapers/tags/'+tags+'/?sort_by=&resolution='+size+'&date_filter=&category=&page='+page.to_s() @app.puts 'getting index for page: '+page.to_s() @app.puts url response = Net::HTTP.get_response(URI.parse(url)) res = response.body res.each_line { |line| f = line.index('wallpapers/view') while f != nil b = line.rindex('"',f) e = line.index('"',b+1) u = line[b+1,eb].gsub('"','') walls[u] = u line = line.sub(u,'') f = line.index('wallpapers/view') end } @app.puts 'got '+walls.size.to_s()+' wallpapers' return walls.keys end def downloadWall(url) @app.puts 'downloading '+url response = Net::HTTP.get_response(URI.parse(url)) res = response.body b = res.index('src',res.rindex('wall_holder'))+5 e = res.index('"',b) img = res[b,eb] self.downloadFile(img) end def downloadFile(url) name = url[url.rindex('/')+1,1000] if File.exists?(@saveTo+'/'+name) @app.puts 'wallpaper already saved '+name @app.changeImage(@saveTo+'/'+name) else @app.puts 'downloading file '+url response = Net::HTTP.get_response(URI.parse(url)) open(@saveTo+'/'+name, 'wb') { |file| file.write(response.body) } @app.puts 'wallpaper saved '+name @app.changeImage(@saveTo+'/'+name) end end def getWallUrl(i,url,size) sizes = {} i = i+1 @app.puts 'getting '+url+' sizes' response = Net::HTTP.get_response(URI.parse(url)) res = response.body res.each_line { |line| f = line.index('wallpapers/download') while f != nil b = line.rindex('\'',f) e = line.index('\'',b+1) u = line[b+1,eb] u = u.gsub('\'','') sizes[u] = u line = line.sub(u,'') f = line.index('wallpapers/download') end } sizef = @size.sub('_','-by-') sizes = sizes.keys() if sizef == '' maxi = 0 max = 0 i = 0 sizes.each { |s| f = s.rindex('/') l = s[f+1,100] l = l.sub('-by-',' ') l = l.split(' ') rs = l[0].to_i()*l[1].to_i() if rs > max maxi = i max = rs end i = i+1 } return sizes[maxi] else sizes.each { |s| if s =~ /#{Regexp.escape(sizef)}$/ return s end } end return sizes[0] end def start @thread = Thread.new { @app.puts "Download started" begin i = 0 p = 1 @app.clearStatus while i < @number.to_i() and not @exit w = self.getIndexPage(p) if w.size == 0 break end w.each { |w| wallu = self.getWallUrl(i,w,self.size) if wallu != nil @app.setStatus(i+1,@number.to_i()) self.downloadWall(wallu) i = i+1 if i >= @number.to_i() or @exit break end end } p = p+1 end @app.puts "" @app.setStatusEnd(i) rescue => e puts e end @app.stopped } end def stop begin @app.puts "Download stopped" if @thread.alive? if @thread == Thread.current Thread.exit(0) else @exit = true end end rescue => e puts e end end end 

Huge class is not it? Yes, it is stuffed with logic, but in fact everything is quite trivial.
The start method simply starts a separate stream that will download our wallpaper, stop stops it. The getIndexPage , getWallUrl , downloadWall methods are specific to theotaku.com and contain quite a bit of logic, but in fact are quite trivial and are used to search for wallpaper by tag, select the desired link to the desired wallpaper size and download these wallpapers.

The result was a Sunday evening spent, a good sense of self-satisfaction, as well as many interesting thoughts about the future of MacRuby as an alternative platform for development on Mac OS X. Of course, the rake was not done and some things could not be done, but I think that the MacRuby platform is beginning to gain popularity and it has a bright future.

The result can be viewed here , but the finished build can be downloaded from here (you should have MacRuby installed)

Thank you for your attention

bye-nii



UPD: Thank you Esthete for parser refactoring, it looks just great, see the latest version on github

Source: https://habr.com/ru/post/133739/


All Articles