! Copyright (c) 2020 Jeffrey Armstrong ! ! Permission is hereby granted, free of charge, to any person obtaining a copy ! of this software and associated documentation files (the "Software"), to deal ! in the Software without restriction, including without limitation the rights ! to use, copy, modify, merge, publish, distribute, sublicense, and/or sell ! copies of the Software, and to permit persons to whom the Software is ! furnished to do so, subject to the following conditions: ! ! The above copyright notice and this permission notice shall be included in ! all copies or substantial portions of the Software. ! ! The Software shall be used for Good, not Evil. ! ! THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ! IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ! FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ! AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ! LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, ! OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE ! SOFTWARE. program gemini use request #ifdef WINDOWS_GUI use ag_render, only: appgraphics_renderer use ag_binary, only: appgraphics_binary_handler #else !use sdl_render use dumb_render use dumb_binary #endif use render use gemini_protocol use layout use file_handling use history use internal_links, only: handle_internal_url, internal_url #ifdef WINDOWS use wsa_network, only: windows_network_startup => startup #endif use favorite_handling implicit none character(256)::initial_site character(1024)::current_url, desired_url, input type(connection)::conn #ifdef WINDOWS_GUI type(appgraphics_renderer)::r type(appgraphics_binary_handler)::bh #else type(dumb_renderer)::r type(dumb_binary_handler)::bh #endif logical::running logical::loaded logical::populated logical::redo_layout integer::return_code character(256)::return_type integer, parameter::io = 100 type(line), pointer::first_line type(location), pointer::locations_visited type(favorite), dimension(:), pointer::faves #ifdef WINDOWS call windows_network_startup() #endif if(command_argument_count() > 0) then call get_command_argument(1, initial_site) if(index(initial_site, "//") > 0) then if(index(initial_site, "gemini://") /= 1) then Print *, "Please provide a gemini URL to start (or nothing at all)" stop end if else initial_site = "gemini://"//trim(initial_site) end if else initial_site = "gemini://gemini.circumlunar.space/" end if running = .true. loaded = .false. redo_layout = .false. call r%initialize() ! Load in any favorites faves => load_favorites() locations_visited => null() desired_url = initial_site current_url = " " first_line => null() open(unit=io, form="formatted", status="scratch", access='stream') do while(running) ! Check for an internal url first if(index(desired_url, internal_url) == 1) then if(handle_internal_url(desired_url, io, faves)) then populated = .true. loaded = .true. return_code = STATUS_SUCCESS return_type = "text/gemini" call update_status(r, desired_url, return_code) end if else if(index(desired_url, "gemini://") /= 1) then redo_layout = r%report_unsupported_protocol(trim(desired_url)) populated = .false. loaded = .true. return_code = STATUS_PROTOCOLFAIL end if if(.not. loaded) then call r%report_status("Requesting "//trim(desired_url)) return_code = request_url(desired_url, io, return_type, bh) populated = .not. is_failure_code(return_code) call update_status(r, desired_url, return_code) end if if(return_code == STATUS_REDIRECT) then call get_redirect_url(io, desired_url) loaded = .false. populated = .false. else if(return_code == STATUS_INPUT) then if(handle_input(r, desired_url, io)) then ! Should force a new load loaded = .false. else loaded = .true. end if else if(populated) then current_url = desired_url desired_url = " " locations_visited => add_location(locations_visited, current_url) if(r%type_supported(return_type)) then ! Only erase if we're loading new lines! if(associated(first_line)) then call free_lines(first_line) end if first_line => load_unit(io, file_type_gemini) loaded = .true. call r%new_page() call r%report_status("Performing Layout") call layout_lines(first_line, r) call r%status_ready() else call r%draw_error("Cannot display file of type "//return_type) call back_location(locations_visited, desired_url) end if else if(redo_layout) then if(associated(first_line)) then call r%report_status("Performing Layout") call layout_lines(first_line, r) call r%status_ready() end if redo_layout = .false. else if(.not. redo_layout .and. is_failure_code(return_code) .and. len_trim(current_url) == 0) then call r%draw_error("Exiting without initial site") running = .false. else if(is_failure_code(return_code)) then ! Only explicitly show an error if it isn't a protocol failure if(return_code /= STATUS_PROTOCOLFAIL) then call r%draw_error("Could not connect to "//desired_url) end if loaded = .true. end if do while(loaded .and. running) select case(r%request_action(input)) case (render_action_quit) running = .false. case (render_action_back) call back_location(locations_visited, desired_url) loaded = .false. case (render_action_layout) if(associated(first_line)) then call r%report_status("Performing Layout") call layout_lines(first_line, r) call r%status_ready() end if case (render_action_rewrap) if(associated(first_line)) then call r%report_status("Performing Layout") call clear_line_breaks(first_line) call layout_lines(first_line, r) call r%status_ready() end if case (render_action_goto) if(index(input, "://") > 0) then desired_url = input else desired_url = current_url call handle_relative_url(desired_url, input) end if loaded = .false. case (render_action_favorite) if(is_favorite(faves, current_url)) then call remove_favorite(faves, current_url) call r%report_status(trim(current_url)//" removed as favorite") else call add_favorite(faves, current_url, current_url) call r%report_status("Added favorite: "//trim(current_url)) end if call save_favorites(faves) end select end do end do close(io) contains subroutine update_status(r, url, code) use gemini_protocol implicit none class(renderer)::r character(*), intent(in)::url integer, intent(in)::code select case (code) case (STATUS_LOCALFAIL) call r%report_status("Network failure loading "//trim(url)) call r%report_displayed_page("...") case (STATUS_INPUT) call r%report_status("Ok (input)") call r%report_displayed_page(url) case (STATUS_SUCCESS) call r%report_status("Ok") call r%report_displayed_page(url) case (STATUS_REDIRECT) call r%report_status("Ok (redirect)") call r%report_displayed_page(url) case (STATUS_TEMPFAIL) call r%report_status("Server reports temporary failure") call r%report_displayed_page("...") case (STATUS_PERMFAIL) call r%report_status("Server reports permanent failure") call r%report_displayed_page("...") case (STATUS_CERTREQ) call r%report_status("Server requesting certificate (unsupported)") call r%report_displayed_page("...") case (STATUS_BADRESPONSE) call r%report_status("Bad response code from server") call r%report_displayed_page("...") end select end subroutine update_status function load_favorites() result(faves) use platform, only: get_favorites_file use favorite_handling, only: read_favorites, favorite implicit none type(favorite), dimension(:), pointer::faves character(260)::filename integer::ios, loadunit call get_favorites_file(filename) open(newunit=loadunit, file=filename, status='old', action='read', iostat=ios) if(ios == 0) then faves => read_favorites(loadunit) close(loadunit) else faves => null() end if end function load_favorites subroutine save_favorites(faves) use platform, only: get_favorites_file use favorite_handling, only: write_favorites, favorite implicit none type(favorite), dimension(:), intent(in), pointer::faves character(260)::filename integer::ios, loadunit call get_favorites_file(filename) open(newunit=loadunit, file=filename, status='unknown', action='write', iostat=ios) if(ios == 0) then call write_favorites(loadunit, faves) close(loadunit) end if end subroutine save_favorites end program gemini